• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "gstplaybackelements.h"
25 #include "gststreamsynchronizer.h"
26 
27 GST_DEBUG_CATEGORY_STATIC (stream_synchronizer_debug);
28 #define GST_CAT_DEFAULT stream_synchronizer_debug
29 
30 #define GST_STREAM_SYNCHRONIZER_LOCK(obj) G_STMT_START {                \
31     GST_TRACE_OBJECT (obj,                                              \
32                     "locking from thread %p",                           \
33                     g_thread_self ());                                  \
34     g_mutex_lock (&GST_STREAM_SYNCHRONIZER_CAST(obj)->lock);            \
35     GST_TRACE_OBJECT (obj,                                              \
36                     "locked from thread %p",                            \
37                     g_thread_self ());                                  \
38 } G_STMT_END
39 
40 #define GST_STREAM_SYNCHRONIZER_UNLOCK(obj) G_STMT_START {              \
41     GST_TRACE_OBJECT (obj,                                              \
42                     "unlocking from thread %p",                         \
43                     g_thread_self ());                                  \
44     g_mutex_unlock (&GST_STREAM_SYNCHRONIZER_CAST(obj)->lock);              \
45 } G_STMT_END
46 
47 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src_%u",
48     GST_PAD_SRC,
49     GST_PAD_SOMETIMES,
50     GST_STATIC_CAPS_ANY);
51 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink_%u",
52     GST_PAD_SINK,
53     GST_PAD_REQUEST,
54     GST_STATIC_CAPS_ANY);
55 
56 #define gst_stream_synchronizer_parent_class parent_class
57 G_DEFINE_TYPE (GstStreamSynchronizer, gst_stream_synchronizer,
58     GST_TYPE_ELEMENT);
59 #define _do_init \
60     playback_element_init (plugin);
61 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (streamsynchronizer, "streamsynchronizer",
62     GST_RANK_NONE, GST_TYPE_STREAM_SYNCHRONIZER, _do_init);
63 
64 typedef struct
65 {
66   GstStreamSynchronizer *transform;
67   guint stream_number;
68   GstPad *srcpad;
69   GstPad *sinkpad;
70   GstSegment segment;
71 
72   gboolean wait;                /* TRUE if waiting/blocking */
73   gboolean is_eos;              /* TRUE if EOS was received */
74   gboolean eos_sent;            /* when EOS was sent downstream */
75   gboolean flushing;            /* set after flush-start and before flush-stop */
76   gboolean seen_data;
77   gboolean send_gap_event;
78   GstClockTime gap_duration;
79 
80   GstStreamFlags flags;
81 
82   GCond stream_finish_cond;
83 
84   /* seqnum of the previously received STREAM_START
85    * default: G_MAXUINT32 */
86   guint32 stream_start_seqnum;
87   guint32 segment_seqnum;
88   guint group_id;
89 
90   gint refcount;
91 } GstSyncStream;
92 
93 static GstSyncStream *
gst_syncstream_ref(GstSyncStream * stream)94 gst_syncstream_ref (GstSyncStream * stream)
95 {
96   g_return_val_if_fail (stream != NULL, NULL);
97   g_atomic_int_add (&stream->refcount, 1);
98   return stream;
99 }
100 
101 static void
gst_syncstream_unref(GstSyncStream * stream)102 gst_syncstream_unref (GstSyncStream * stream)
103 {
104   g_return_if_fail (stream != NULL);
105   g_return_if_fail (stream->refcount > 0);
106 
107   if (g_atomic_int_dec_and_test (&stream->refcount))
108     g_slice_free (GstSyncStream, stream);
109 }
110 
111 G_BEGIN_DECLS
112 #define GST_TYPE_STREAMSYNC_PAD              (gst_streamsync_pad_get_type ())
113 #define GST_IS_STREAMSYNC_PAD(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_STREAMSYNC_PAD))
114 #define GST_IS_STREAMSYNC_PAD_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_STREAMSYNC_PAD))
115 #define GST_STREAMSYNC_PAD(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_STREAMSYNC_PAD, GstStreamSyncPad))
116 #define GST_STREAMSYNC_PAD_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_STREAMSYNC_PAD, GstStreamSyncPadClass))
117 typedef struct _GstStreamSyncPad GstStreamSyncPad;
118 typedef struct _GstStreamSyncPadClass GstStreamSyncPadClass;
119 
120 struct _GstStreamSyncPad
121 {
122   GstPad parent;
123 
124   GstSyncStream *stream;
125 
126   /* Since we need to access data associated with a pad in this
127    * element, it's important to manage the respective lifetimes of the
128    * stored pad data and the pads themselves. Pad deactivation happens
129    * without mutual exclusion to the use of pad data in this element.
130    *
131    * The approach here is to have the sinkpad (the request pad) hold a
132    * strong reference onto the srcpad (so that it stays alive until
133    * the last pad is destroyed). Similarly the srcpad has a weak
134    * reference to the sinkpad (request pad) to ensure it knows when
135    * the pads are destroyed, since the pad data may be requested from
136    * either the srcpad or the sinkpad. This avoids a nasty set of
137    * potential race conditions.
138    *
139    * The code is arranged so that in the srcpad, the pad pointer is
140    * always NULL (not used) and in the sinkpad, the otherpad is always
141    * NULL. */
142   GstPad *pad;
143   GWeakRef otherpad;
144 };
145 
146 struct _GstStreamSyncPadClass
147 {
148   GstPadClass parent_class;
149 };
150 
151 static GType gst_streamsync_pad_get_type (void);
152 static GstSyncStream *gst_streamsync_pad_get_stream (GstPad * pad);
153 
154 G_END_DECLS
155 #define GST_STREAMSYNC_PAD_CAST(obj)         ((GstStreamSyncPad *)obj)
156   G_DEFINE_TYPE (GstStreamSyncPad, gst_streamsync_pad, GST_TYPE_PAD);
157 
158 static void gst_streamsync_pad_dispose (GObject * object);
159 
160 static void
gst_streamsync_pad_class_init(GstStreamSyncPadClass * klass)161 gst_streamsync_pad_class_init (GstStreamSyncPadClass * klass)
162 {
163   GObjectClass *gobject_class;
164   gobject_class = G_OBJECT_CLASS (klass);
165   gobject_class->dispose = gst_streamsync_pad_dispose;
166 }
167 
168 static void
gst_streamsync_pad_init(GstStreamSyncPad * ppad)169 gst_streamsync_pad_init (GstStreamSyncPad * ppad)
170 {
171 }
172 
173 static void
gst_streamsync_pad_dispose(GObject * object)174 gst_streamsync_pad_dispose (GObject * object)
175 {
176   GstStreamSyncPad *spad = GST_STREAMSYNC_PAD_CAST (object);
177 
178   if (GST_PAD_DIRECTION (spad) == GST_PAD_SINK)
179     gst_clear_object (&spad->pad);
180   else
181     g_weak_ref_clear (&spad->otherpad);
182 
183   g_clear_pointer (&spad->stream, gst_syncstream_unref);
184 
185   G_OBJECT_CLASS (gst_streamsync_pad_parent_class)->dispose (object);
186 }
187 
188 static GstPad *
gst_streamsync_pad_new_from_template(GstPadTemplate * templ,const gchar * name)189 gst_streamsync_pad_new_from_template (GstPadTemplate * templ,
190     const gchar * name)
191 {
192   g_return_val_if_fail (GST_IS_PAD_TEMPLATE (templ), NULL);
193 
194   return GST_PAD_CAST (g_object_new (GST_TYPE_STREAMSYNC_PAD,
195           "name", name, "direction", templ->direction, "template", templ,
196           NULL));
197 }
198 
199 static GstPad *
gst_streamsync_pad_new_from_static_template(GstStaticPadTemplate * templ,const gchar * name)200 gst_streamsync_pad_new_from_static_template (GstStaticPadTemplate * templ,
201     const gchar * name)
202 {
203   GstPad *pad;
204   GstPadTemplate *template;
205 
206   template = gst_static_pad_template_get (templ);
207   pad = gst_streamsync_pad_new_from_template (template, name);
208   gst_object_unref (template);
209 
210   return pad;
211 }
212 
213 static GstSyncStream *
gst_streamsync_pad_get_stream(GstPad * pad)214 gst_streamsync_pad_get_stream (GstPad * pad)
215 {
216   GstStreamSyncPad *spad = GST_STREAMSYNC_PAD_CAST (pad);
217   return gst_syncstream_ref (spad->stream);
218 }
219 
220 static GstPad *
gst_stream_get_other_pad_from_pad(GstStreamSynchronizer * self,GstPad * pad)221 gst_stream_get_other_pad_from_pad (GstStreamSynchronizer * self, GstPad * pad)
222 {
223   GstStreamSyncPad *spad = GST_STREAMSYNC_PAD_CAST (pad);
224   GstPad *opad = NULL;
225 
226   if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK)
227     opad = gst_object_ref (spad->pad);
228   else
229     opad = g_weak_ref_get (&spad->otherpad);
230 
231   if (!opad)
232     GST_WARNING_OBJECT (pad, "Trying to get other pad after releasing");
233 
234   return opad;
235 }
236 
237 /* Generic pad functions */
238 static GstIterator *
gst_stream_synchronizer_iterate_internal_links(GstPad * pad,GstObject * parent)239 gst_stream_synchronizer_iterate_internal_links (GstPad * pad,
240     GstObject * parent)
241 {
242   GstIterator *it = NULL;
243   GstPad *opad;
244 
245   opad =
246       gst_stream_get_other_pad_from_pad (GST_STREAM_SYNCHRONIZER (parent), pad);
247   if (opad) {
248     GValue value = { 0, };
249 
250     g_value_init (&value, GST_TYPE_PAD);
251     g_value_set_object (&value, opad);
252     it = gst_iterator_new_single (GST_TYPE_PAD, &value);
253     g_value_unset (&value);
254     gst_object_unref (opad);
255   }
256 
257   return it;
258 }
259 
260 static GstEvent *
set_event_rt_offset(GstStreamSynchronizer * self,GstPad * pad,GstEvent * event)261 set_event_rt_offset (GstStreamSynchronizer * self, GstPad * pad,
262     GstEvent * event)
263 {
264   gint64 running_time_diff;
265   GstSyncStream *stream;
266 
267   GST_STREAM_SYNCHRONIZER_LOCK (self);
268   stream = gst_streamsync_pad_get_stream (pad);
269   running_time_diff = stream->segment.base;
270   gst_syncstream_unref (stream);
271   GST_STREAM_SYNCHRONIZER_UNLOCK (self);
272 
273   if (running_time_diff != -1) {
274     gint64 offset;
275 
276     event = gst_event_make_writable (event);
277     offset = gst_event_get_running_time_offset (event);
278     if (GST_PAD_IS_SRC (pad))
279       offset -= running_time_diff;
280     else
281       offset += running_time_diff;
282 
283     gst_event_set_running_time_offset (event, offset);
284   }
285 
286   return event;
287 }
288 
289 /* srcpad functions */
290 static gboolean
gst_stream_synchronizer_src_event(GstPad * pad,GstObject * parent,GstEvent * event)291 gst_stream_synchronizer_src_event (GstPad * pad, GstObject * parent,
292     GstEvent * event)
293 {
294   GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (parent);
295   gboolean ret = FALSE;
296 
297   GST_LOG_OBJECT (pad, "Handling event %s: %" GST_PTR_FORMAT,
298       GST_EVENT_TYPE_NAME (event), event);
299 
300   event = set_event_rt_offset (self, pad, event);
301 
302   ret = gst_pad_event_default (pad, parent, event);
303 
304   return ret;
305 }
306 
307 /* must be called with the STREAM_SYNCHRONIZER_LOCK */
308 static gboolean
gst_stream_synchronizer_wait(GstStreamSynchronizer * self,GstPad * pad)309 gst_stream_synchronizer_wait (GstStreamSynchronizer * self, GstPad * pad)
310 {
311   gboolean ret = FALSE;
312   GstSyncStream *stream;
313 
314   stream = gst_streamsync_pad_get_stream (pad);
315 
316   while (!self->eos && !self->flushing) {
317     if (stream->flushing) {
318       GST_DEBUG_OBJECT (pad, "Flushing");
319       break;
320     }
321     if (!stream->wait) {
322       GST_DEBUG_OBJECT (pad, "Stream not waiting anymore");
323       break;
324     }
325 
326     if (stream->send_gap_event) {
327       GstEvent *event;
328 
329       if (!GST_CLOCK_TIME_IS_VALID (stream->segment.position)) {
330         GST_WARNING_OBJECT (pad, "Have no position and can't send GAP event");
331         stream->send_gap_event = FALSE;
332         continue;
333       }
334 
335       event =
336           gst_event_new_gap (stream->segment.position, stream->gap_duration);
337       GST_DEBUG_OBJECT (pad,
338           "Send GAP event, position: %" GST_TIME_FORMAT " duration: %"
339           GST_TIME_FORMAT, GST_TIME_ARGS (stream->segment.position),
340           GST_TIME_ARGS (stream->gap_duration));
341 
342       /* drop lock when sending GAP event, which may block in e.g. preroll */
343       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
344       ret = gst_pad_push_event (pad, event);
345       GST_STREAM_SYNCHRONIZER_LOCK (self);
346       if (!ret) {
347         gst_syncstream_unref (stream);
348         return ret;
349       }
350       stream->send_gap_event = FALSE;
351 
352       /* force a check on the loop conditions as we unlocked a
353        * few lines above and those variables could have changed */
354       continue;
355     }
356 
357     g_cond_wait (&stream->stream_finish_cond, &self->lock);
358   }
359 
360   gst_syncstream_unref (stream);
361   return TRUE;
362 }
363 
364 /* sinkpad functions */
365 static gboolean
gst_stream_synchronizer_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)366 gst_stream_synchronizer_sink_event (GstPad * pad, GstObject * parent,
367     GstEvent * event)
368 {
369   GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (parent);
370   gboolean ret = FALSE;
371 
372   GST_LOG_OBJECT (pad, "Handling event %s: %" GST_PTR_FORMAT,
373       GST_EVENT_TYPE_NAME (event), event);
374 
375   switch (GST_EVENT_TYPE (event)) {
376     case GST_EVENT_STREAM_START:
377     {
378       GstSyncStream *stream, *ostream;
379       guint32 seqnum = gst_event_get_seqnum (event);
380       guint group_id;
381       gboolean have_group_id;
382       GList *l;
383       gboolean all_wait = TRUE;
384       gboolean new_stream = TRUE;
385 
386       have_group_id = gst_event_parse_group_id (event, &group_id);
387 
388       GST_STREAM_SYNCHRONIZER_LOCK (self);
389       self->have_group_id &= have_group_id;
390       have_group_id = self->have_group_id;
391 
392       stream = gst_streamsync_pad_get_stream (pad);
393 
394       gst_event_parse_stream_flags (event, &stream->flags);
395 
396       if ((have_group_id && stream->group_id != group_id) || (!have_group_id
397               && stream->stream_start_seqnum != seqnum)) {
398         stream->is_eos = FALSE;
399         stream->eos_sent = FALSE;
400         stream->flushing = FALSE;
401         stream->stream_start_seqnum = seqnum;
402         stream->group_id = group_id;
403 
404         if (!have_group_id) {
405           /* Check if this belongs to a stream that is already there,
406            * e.g. we got the visualizations for an audio stream */
407           for (l = self->streams; l; l = l->next) {
408             ostream = l->data;
409 
410             if (ostream != stream && ostream->stream_start_seqnum == seqnum
411                 && !ostream->wait) {
412               new_stream = FALSE;
413               break;
414             }
415           }
416 
417           if (!new_stream) {
418             GST_DEBUG_OBJECT (pad,
419                 "Stream %d belongs to running stream %d, no waiting",
420                 stream->stream_number, ostream->stream_number);
421             stream->wait = FALSE;
422 
423             GST_STREAM_SYNCHRONIZER_UNLOCK (self);
424             break;
425           }
426         } else if (group_id == self->group_id) {
427           GST_DEBUG_OBJECT (pad, "Stream %d belongs to running group %d, "
428               "no waiting", stream->stream_number, group_id);
429           GST_STREAM_SYNCHRONIZER_UNLOCK (self);
430           break;
431         }
432 
433         GST_DEBUG_OBJECT (pad, "Stream %d changed", stream->stream_number);
434 
435         stream->wait = TRUE;
436 
437         for (l = self->streams; l; l = l->next) {
438           GstSyncStream *ostream = l->data;
439 
440           all_wait = all_wait && ((ostream->flags & GST_STREAM_FLAG_SPARSE)
441               || (ostream->wait && (!have_group_id
442                       || ostream->group_id == group_id)));
443           if (!all_wait)
444             break;
445         }
446 
447         if (all_wait) {
448           gint64 position = 0;
449 
450           if (have_group_id)
451             GST_DEBUG_OBJECT (self,
452                 "All streams have changed to group id %u -- unblocking",
453                 group_id);
454           else
455             GST_DEBUG_OBJECT (self, "All streams have changed -- unblocking");
456 
457           self->group_id = group_id;
458 
459           for (l = self->streams; l; l = l->next) {
460             GstSyncStream *ostream = l->data;
461             gint64 stop_running_time;
462             gint64 position_running_time;
463 
464             ostream->wait = FALSE;
465 
466             if (ostream->segment.format == GST_FORMAT_TIME) {
467               if (ostream->segment.rate > 0)
468                 stop_running_time =
469                     gst_segment_to_running_time (&ostream->segment,
470                     GST_FORMAT_TIME, ostream->segment.stop);
471               else
472                 stop_running_time =
473                     gst_segment_to_running_time (&ostream->segment,
474                     GST_FORMAT_TIME, ostream->segment.start);
475 
476               position_running_time =
477                   gst_segment_to_running_time (&ostream->segment,
478                   GST_FORMAT_TIME, ostream->segment.position);
479 
480               position_running_time =
481                   MAX (position_running_time, stop_running_time);
482 
483               if (ostream->segment.rate > 0)
484                 position_running_time -=
485                     gst_segment_to_running_time (&ostream->segment,
486                     GST_FORMAT_TIME, ostream->segment.start);
487               else
488                 position_running_time -=
489                     gst_segment_to_running_time (&ostream->segment,
490                     GST_FORMAT_TIME, ostream->segment.stop);
491 
492               position_running_time = MAX (0, position_running_time);
493 
494               position = MAX (position, position_running_time);
495             }
496           }
497 
498           self->group_start_time += position;
499 
500           GST_DEBUG_OBJECT (self, "New group start time: %" GST_TIME_FORMAT,
501               GST_TIME_ARGS (self->group_start_time));
502 
503           for (l = self->streams; l; l = l->next) {
504             GstSyncStream *ostream = l->data;
505             ostream->wait = FALSE;
506             g_cond_broadcast (&ostream->stream_finish_cond);
507           }
508         }
509       }
510 
511       gst_syncstream_unref (stream);
512       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
513       break;
514     }
515     case GST_EVENT_SEGMENT:{
516       GstSyncStream *stream;
517       GstSegment segment;
518 
519       gst_event_copy_segment (event, &segment);
520 
521       GST_STREAM_SYNCHRONIZER_LOCK (self);
522 
523       gst_stream_synchronizer_wait (self, pad);
524 
525       if (self->shutdown) {
526         GST_STREAM_SYNCHRONIZER_UNLOCK (self);
527         gst_event_unref (event);
528         goto done;
529       }
530 
531       stream = gst_streamsync_pad_get_stream (pad);
532       if (segment.format == GST_FORMAT_TIME) {
533         GST_DEBUG_OBJECT (pad,
534             "New stream, updating base from %" GST_TIME_FORMAT " to %"
535             GST_TIME_FORMAT, GST_TIME_ARGS (segment.base),
536             GST_TIME_ARGS (segment.base + self->group_start_time));
537         segment.base += self->group_start_time;
538 
539         GST_DEBUG_OBJECT (pad, "Segment was: %" GST_SEGMENT_FORMAT,
540             &stream->segment);
541         gst_segment_copy_into (&segment, &stream->segment);
542         GST_DEBUG_OBJECT (pad, "Segment now is: %" GST_SEGMENT_FORMAT,
543             &stream->segment);
544         stream->segment_seqnum = gst_event_get_seqnum (event);
545 
546         GST_DEBUG_OBJECT (pad, "Stream start running time: %" GST_TIME_FORMAT,
547             GST_TIME_ARGS (stream->segment.base));
548         {
549           GstEvent *tmpev;
550 
551           tmpev = gst_event_new_segment (&stream->segment);
552           gst_event_set_seqnum (tmpev, stream->segment_seqnum);
553           gst_event_unref (event);
554           event = tmpev;
555         }
556       } else if (stream) {
557         GST_WARNING_OBJECT (pad, "Non-TIME segment: %s",
558             gst_format_get_name (segment.format));
559         gst_segment_init (&stream->segment, GST_FORMAT_UNDEFINED);
560       }
561       gst_syncstream_unref (stream);
562       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
563       break;
564     }
565     case GST_EVENT_FLUSH_START:{
566       GstSyncStream *stream;
567 
568       GST_STREAM_SYNCHRONIZER_LOCK (self);
569       stream = gst_streamsync_pad_get_stream (pad);
570       self->eos = FALSE;
571       GST_DEBUG_OBJECT (pad, "Flushing streams");
572       stream->flushing = TRUE;
573       g_cond_broadcast (&stream->stream_finish_cond);
574       gst_syncstream_unref (stream);
575       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
576       break;
577     }
578     case GST_EVENT_FLUSH_STOP:{
579       GstSyncStream *stream;
580       GList *l;
581       GstClockTime new_group_start_time = 0;
582 
583       GST_STREAM_SYNCHRONIZER_LOCK (self);
584       stream = gst_streamsync_pad_get_stream (pad);
585       GST_DEBUG_OBJECT (pad, "Resetting segment for stream %d",
586           stream->stream_number);
587       gst_segment_init (&stream->segment, GST_FORMAT_UNDEFINED);
588 
589       stream->is_eos = FALSE;
590       stream->eos_sent = FALSE;
591       stream->flushing = FALSE;
592       stream->wait = FALSE;
593       g_cond_broadcast (&stream->stream_finish_cond);
594 
595       for (l = self->streams; l; l = l->next) {
596         GstSyncStream *ostream = l->data;
597         GstClockTime start_running_time;
598 
599         if (ostream == stream || ostream->flushing)
600           continue;
601 
602         if (ostream->segment.format == GST_FORMAT_TIME) {
603           if (ostream->segment.rate > 0)
604             start_running_time =
605                 gst_segment_to_running_time (&ostream->segment,
606                 GST_FORMAT_TIME, ostream->segment.start);
607           else
608             start_running_time =
609                 gst_segment_to_running_time (&ostream->segment,
610                 GST_FORMAT_TIME, ostream->segment.stop);
611 
612           new_group_start_time = MAX (new_group_start_time, start_running_time);
613         }
614       }
615 
616       GST_DEBUG_OBJECT (pad,
617           "Updating group start time from %" GST_TIME_FORMAT " to %"
618           GST_TIME_FORMAT, GST_TIME_ARGS (self->group_start_time),
619           GST_TIME_ARGS (new_group_start_time));
620       self->group_start_time = new_group_start_time;
621 
622       gst_syncstream_unref (stream);
623       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
624       break;
625     }
626       /* unblocking EOS wait when track switch. */
627     case GST_EVENT_CUSTOM_DOWNSTREAM_OOB:{
628       if (gst_event_has_name (event, "playsink-custom-video-flush")
629           || gst_event_has_name (event, "playsink-custom-audio-flush")
630           || gst_event_has_name (event, "playsink-custom-subtitle-flush")) {
631         GstSyncStream *stream;
632 
633         GST_STREAM_SYNCHRONIZER_LOCK (self);
634         stream = gst_streamsync_pad_get_stream (pad);
635         stream->is_eos = FALSE;
636         stream->eos_sent = FALSE;
637         stream->wait = FALSE;
638         g_cond_broadcast (&stream->stream_finish_cond);
639         gst_syncstream_unref (stream);
640         GST_STREAM_SYNCHRONIZER_UNLOCK (self);
641       }
642       break;
643     }
644     case GST_EVENT_EOS:{
645       GstSyncStream *stream;
646       GList *l;
647       gboolean all_eos = TRUE;
648       gboolean seen_data;
649       GSList *pads = NULL;
650       GstPad *srcpad;
651       GstClockTime timestamp;
652       guint32 seqnum;
653 
654       GST_STREAM_SYNCHRONIZER_LOCK (self);
655       stream = gst_streamsync_pad_get_stream (pad);
656 
657       GST_DEBUG_OBJECT (pad, "Have EOS for stream %d", stream->stream_number);
658       stream->is_eos = TRUE;
659 
660       seen_data = stream->seen_data;
661       srcpad = gst_object_ref (stream->srcpad);
662       seqnum = stream->segment_seqnum;
663 
664       if (seen_data && stream->segment.position != -1)
665         timestamp = stream->segment.position;
666       else if (stream->segment.rate < 0.0 || stream->segment.stop == -1)
667         timestamp = stream->segment.start;
668       else
669         timestamp = stream->segment.stop;
670 
671       stream->segment.position = timestamp;
672 
673       for (l = self->streams; l; l = l->next) {
674         GstSyncStream *ostream = l->data;
675 
676         all_eos = all_eos && ostream->is_eos;
677         if (!all_eos)
678           break;
679       }
680 
681       if (all_eos) {
682         GST_DEBUG_OBJECT (self, "All streams are EOS -- forwarding");
683         self->eos = TRUE;
684         for (l = self->streams; l; l = l->next) {
685           GstSyncStream *ostream = l->data;
686           /* local snapshot of current pads */
687           gst_object_ref (ostream->srcpad);
688           pads = g_slist_prepend (pads, ostream->srcpad);
689         }
690       }
691       if (pads) {
692         GstPad *pad;
693         GSList *epad;
694         GstSyncStream *ostream;
695 
696         ret = TRUE;
697         epad = pads;
698         while (epad) {
699           pad = epad->data;
700           ostream = gst_streamsync_pad_get_stream (pad);
701           g_cond_broadcast (&ostream->stream_finish_cond);
702           gst_syncstream_unref (ostream);
703           gst_object_unref (pad);
704           epad = g_slist_next (epad);
705         }
706         g_slist_free (pads);
707       } else {
708         if (seen_data) {
709 #ifdef OHOS_OPT_COMPAT
710           /**
711            * ohos.opt.compat.0009
712            * drop lock when sending eos, which may block in e.g. preroll.
713            * eos coming for one of streams when chaning state to pause, which may
714            * leads the preroll to be blocked.
715            */
716           GST_DEBUG_OBJECT (pad, "send EOS event, in");
717           GST_STREAM_SYNCHRONIZER_UNLOCK (self);
718           ret = gst_pad_push_event (srcpad, gst_event_new_eos ());
719           GST_STREAM_SYNCHRONIZER_LOCK (self);
720           stream->eos_sent = TRUE;
721 #else
722           stream->send_gap_event = TRUE;
723           stream->gap_duration = GST_CLOCK_TIME_NONE;
724           stream->wait = TRUE;
725           ret = gst_stream_synchronizer_wait (self, srcpad);
726 #endif
727         }
728       }
729 
730       /* send eos if haven't seen data. seen_data will be true if data buffer
731        * of the track have received in anytime. sink is ready if seen_data is
732        * true, so can send GAP event. Will send EOS if sink isn't ready. The
733        * scenario for the case is one track haven't any media data and then
734        * send EOS. Or no any valid media data in one track, so decoder can't
735        * get valid CAPS for the track. sink can't ready without received CAPS.*/
736       if (!seen_data || self->eos) {
737         GstEvent *topush;
738         GST_DEBUG_OBJECT (pad, "send EOS event");
739         /* drop lock when sending eos, which may block in e.g. preroll */
740         topush = gst_event_new_eos ();
741         gst_event_set_seqnum (topush, seqnum);
742         GST_STREAM_SYNCHRONIZER_UNLOCK (self);
743         ret = gst_pad_push_event (srcpad, topush);
744         GST_STREAM_SYNCHRONIZER_LOCK (self);
745         stream = gst_streamsync_pad_get_stream (pad);
746         stream->eos_sent = TRUE;
747         gst_syncstream_unref (stream);
748       }
749 
750       gst_object_unref (srcpad);
751       gst_event_unref (event);
752       gst_syncstream_unref (stream);
753       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
754       goto done;
755     }
756     default:
757       break;
758   }
759 
760   event = set_event_rt_offset (self, pad, event);
761 
762   ret = gst_pad_event_default (pad, parent, event);
763 
764 done:
765 
766   return ret;
767 }
768 
769 static GstFlowReturn
gst_stream_synchronizer_sink_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)770 gst_stream_synchronizer_sink_chain (GstPad * pad, GstObject * parent,
771     GstBuffer * buffer)
772 {
773   GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (parent);
774   GstPad *opad;
775   GstFlowReturn ret = GST_FLOW_ERROR;
776   GstSyncStream *stream;
777   GstClockTime duration = GST_CLOCK_TIME_NONE;
778   GstClockTime timestamp = GST_CLOCK_TIME_NONE;
779   GstClockTime timestamp_end = GST_CLOCK_TIME_NONE;
780 
781   GST_LOG_OBJECT (pad, "Handling buffer %p: size=%" G_GSIZE_FORMAT
782       ", timestamp=%" GST_TIME_FORMAT " duration=%" GST_TIME_FORMAT
783       " offset=%" G_GUINT64_FORMAT " offset_end=%" G_GUINT64_FORMAT,
784       buffer, gst_buffer_get_size (buffer),
785       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
786       GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)),
787       GST_BUFFER_OFFSET (buffer), GST_BUFFER_OFFSET_END (buffer));
788 
789   timestamp = GST_BUFFER_TIMESTAMP (buffer);
790   duration = GST_BUFFER_DURATION (buffer);
791   if (GST_CLOCK_TIME_IS_VALID (timestamp)
792       && GST_CLOCK_TIME_IS_VALID (duration))
793     timestamp_end = timestamp + duration;
794 
795   GST_STREAM_SYNCHRONIZER_LOCK (self);
796   stream = gst_streamsync_pad_get_stream (pad);
797 
798   stream->seen_data = TRUE;
799   if (stream->segment.format == GST_FORMAT_TIME
800       && GST_CLOCK_TIME_IS_VALID (timestamp)) {
801     GST_LOG_OBJECT (pad,
802         "Updating position from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
803         GST_TIME_ARGS (stream->segment.position), GST_TIME_ARGS (timestamp));
804     if (stream->segment.rate > 0.0)
805       stream->segment.position = timestamp;
806     else
807       stream->segment.position = timestamp_end;
808   }
809 
810   gst_syncstream_unref (stream);
811   GST_STREAM_SYNCHRONIZER_UNLOCK (self);
812 
813   opad = gst_stream_get_other_pad_from_pad (self, pad);
814   if (opad) {
815     ret = gst_pad_push (opad, buffer);
816     gst_object_unref (opad);
817   }
818 
819   GST_LOG_OBJECT (pad, "Push returned: %s", gst_flow_get_name (ret));
820   if (ret == GST_FLOW_OK) {
821     GList *l;
822 
823     GST_STREAM_SYNCHRONIZER_LOCK (self);
824     stream = gst_streamsync_pad_get_stream (pad);
825     if (stream->segment.format == GST_FORMAT_TIME) {
826       GstClockTime position;
827 
828       if (stream->segment.rate > 0.0)
829         position = timestamp_end;
830       else
831         position = timestamp;
832 
833       if (GST_CLOCK_TIME_IS_VALID (position)) {
834         GST_LOG_OBJECT (pad,
835             "Updating position from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
836             GST_TIME_ARGS (stream->segment.position), GST_TIME_ARGS (position));
837         stream->segment.position = position;
838       }
839     }
840 
841     /* Advance EOS streams if necessary. For non-EOS
842      * streams the demuxers should already do this! */
843     if (!GST_CLOCK_TIME_IS_VALID (timestamp_end) &&
844         GST_CLOCK_TIME_IS_VALID (timestamp)) {
845       timestamp_end = timestamp + GST_SECOND;
846     }
847 
848     for (l = self->streams; l; l = l->next) {
849       GstSyncStream *ostream = l->data;
850       gint64 position;
851 
852       if (!ostream->is_eos || ostream->eos_sent ||
853           ostream->segment.format != GST_FORMAT_TIME)
854         continue;
855 
856       if (ostream->segment.position != -1)
857         position = ostream->segment.position;
858       else
859         position = ostream->segment.start;
860 
861       /* Is there a 1 second lag? */
862       if (position != -1 && GST_CLOCK_TIME_IS_VALID (timestamp_end) &&
863           position + GST_SECOND < timestamp_end) {
864         gint64 new_start;
865 
866         new_start = timestamp_end - GST_SECOND;
867 
868         GST_DEBUG_OBJECT (ostream->sinkpad,
869             "Advancing stream %u from %" GST_TIME_FORMAT " to %"
870             GST_TIME_FORMAT, ostream->stream_number, GST_TIME_ARGS (position),
871             GST_TIME_ARGS (new_start));
872 
873         ostream->segment.position = new_start;
874 
875         ostream->send_gap_event = TRUE;
876         ostream->gap_duration = new_start - position;
877         g_cond_broadcast (&ostream->stream_finish_cond);
878       }
879     }
880     gst_syncstream_unref (stream);
881     GST_STREAM_SYNCHRONIZER_UNLOCK (self);
882   }
883 
884   return ret;
885 }
886 
887 /* Must be called with lock! */
888 static GstPad *
gst_stream_synchronizer_new_pad(GstStreamSynchronizer * sync)889 gst_stream_synchronizer_new_pad (GstStreamSynchronizer * sync)
890 {
891   GstSyncStream *stream = NULL;
892   GstStreamSyncPad *sinkpad, *srcpad;
893   gchar *tmp;
894 
895   stream = g_slice_new0 (GstSyncStream);
896   stream->transform = sync;
897   stream->stream_number = sync->current_stream_number;
898   g_cond_init (&stream->stream_finish_cond);
899   stream->stream_start_seqnum = G_MAXUINT32;
900   stream->segment_seqnum = G_MAXUINT32;
901   stream->group_id = G_MAXUINT;
902   stream->seen_data = FALSE;
903   stream->send_gap_event = FALSE;
904   stream->refcount = 1;
905 
906   tmp = g_strdup_printf ("sink_%u", sync->current_stream_number);
907   stream->sinkpad =
908       gst_streamsync_pad_new_from_static_template (&sinktemplate, tmp);
909   g_free (tmp);
910 
911   GST_STREAMSYNC_PAD_CAST (stream->sinkpad)->stream =
912       gst_syncstream_ref (stream);
913 
914   gst_pad_set_iterate_internal_links_function (stream->sinkpad,
915       GST_DEBUG_FUNCPTR (gst_stream_synchronizer_iterate_internal_links));
916   gst_pad_set_event_function (stream->sinkpad,
917       GST_DEBUG_FUNCPTR (gst_stream_synchronizer_sink_event));
918   gst_pad_set_chain_function (stream->sinkpad,
919       GST_DEBUG_FUNCPTR (gst_stream_synchronizer_sink_chain));
920   GST_PAD_SET_PROXY_CAPS (stream->sinkpad);
921   GST_PAD_SET_PROXY_ALLOCATION (stream->sinkpad);
922   GST_PAD_SET_PROXY_SCHEDULING (stream->sinkpad);
923 
924   tmp = g_strdup_printf ("src_%u", sync->current_stream_number);
925   stream->srcpad =
926       gst_streamsync_pad_new_from_static_template (&srctemplate, tmp);
927   g_free (tmp);
928 
929   GST_STREAMSYNC_PAD_CAST (stream->srcpad)->stream =
930       gst_syncstream_ref (stream);
931 
932   sinkpad = GST_STREAMSYNC_PAD_CAST (stream->sinkpad);
933   srcpad = GST_STREAMSYNC_PAD_CAST (stream->srcpad);
934   /* Hold a strong reference from the sink (request pad) to the src to
935    * ensure a predicatable destruction order */
936   sinkpad->pad = gst_object_ref (srcpad);
937   /* And a weak reference from the src to the sink, to know when pad
938    * release is occuring, and to ensure we do not try and take
939    * references to inactive / destructing streams. */
940   g_weak_ref_init (&srcpad->otherpad, stream->sinkpad);
941 
942   gst_pad_set_iterate_internal_links_function (stream->srcpad,
943       GST_DEBUG_FUNCPTR (gst_stream_synchronizer_iterate_internal_links));
944   gst_pad_set_event_function (stream->srcpad,
945       GST_DEBUG_FUNCPTR (gst_stream_synchronizer_src_event));
946   GST_PAD_SET_PROXY_CAPS (stream->srcpad);
947   GST_PAD_SET_PROXY_ALLOCATION (stream->srcpad);
948   GST_PAD_SET_PROXY_SCHEDULING (stream->srcpad);
949 
950   gst_segment_init (&stream->segment, GST_FORMAT_UNDEFINED);
951 
952   GST_STREAM_SYNCHRONIZER_UNLOCK (sync);
953 
954   /* Add pads and activate unless we're going to NULL */
955   g_rec_mutex_lock (GST_STATE_GET_LOCK (sync));
956   if (GST_STATE_TARGET (sync) != GST_STATE_NULL) {
957     gst_pad_set_active (stream->srcpad, TRUE);
958     gst_pad_set_active (stream->sinkpad, TRUE);
959   }
960   gst_element_add_pad (GST_ELEMENT_CAST (sync), stream->srcpad);
961   gst_element_add_pad (GST_ELEMENT_CAST (sync), stream->sinkpad);
962   g_rec_mutex_unlock (GST_STATE_GET_LOCK (sync));
963 
964   GST_STREAM_SYNCHRONIZER_LOCK (sync);
965 
966   sync->streams = g_list_prepend (sync->streams, g_steal_pointer (&stream));
967   sync->current_stream_number++;
968 
969   return GST_PAD_CAST (sinkpad);
970 }
971 
972 /* GstElement vfuncs */
973 static GstPad *
gst_stream_synchronizer_request_new_pad(GstElement * element,GstPadTemplate * temp,const gchar * name,const GstCaps * caps)974 gst_stream_synchronizer_request_new_pad (GstElement * element,
975     GstPadTemplate * temp, const gchar * name, const GstCaps * caps)
976 {
977   GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (element);
978   GstPad *request_pad;
979 
980   GST_STREAM_SYNCHRONIZER_LOCK (self);
981   GST_DEBUG_OBJECT (self, "Requesting new pad for stream %d",
982       self->current_stream_number);
983 
984   request_pad = gst_stream_synchronizer_new_pad (self);
985 
986   GST_STREAM_SYNCHRONIZER_UNLOCK (self);
987 
988   return request_pad;
989 }
990 
991 /* Must be called with lock! */
992 static void
gst_stream_synchronizer_release_stream(GstStreamSynchronizer * self,GstSyncStream * stream)993 gst_stream_synchronizer_release_stream (GstStreamSynchronizer * self,
994     GstSyncStream * stream)
995 {
996   GList *l;
997 
998   GST_DEBUG_OBJECT (self, "Releasing stream %d", stream->stream_number);
999 
1000   for (l = self->streams; l; l = l->next) {
1001     if (l->data == stream) {
1002       self->streams = g_list_delete_link (self->streams, l);
1003       break;
1004     }
1005   }
1006   g_assert (l != NULL);
1007   if (self->streams == NULL) {
1008     self->have_group_id = TRUE;
1009     self->group_id = G_MAXUINT;
1010   }
1011 
1012   /* we can drop the lock, since stream exists now only local.
1013    * Moreover, we should drop, to prevent deadlock with STREAM_LOCK
1014    * (due to reverse lock order) when deactivating pads */
1015   GST_STREAM_SYNCHRONIZER_UNLOCK (self);
1016 
1017   gst_pad_set_active (stream->srcpad, FALSE);
1018   gst_element_remove_pad (GST_ELEMENT_CAST (self), stream->srcpad);
1019   gst_pad_set_active (stream->sinkpad, FALSE);
1020   gst_element_remove_pad (GST_ELEMENT_CAST (self), stream->sinkpad);
1021 
1022   g_cond_clear (&stream->stream_finish_cond);
1023 
1024   /* Release the ref maintaining validity in the streams list */
1025   gst_syncstream_unref (stream);
1026 
1027   /* NOTE: In theory we have to check here if all streams
1028    * are EOS but the one that was removed wasn't and then
1029    * send EOS downstream. But due to the way how playsink
1030    * works this is not necessary and will only cause problems
1031    * for gapless playback. playsink will only add/remove pads
1032    * when it's reconfigured, which happens when the streams
1033    * change
1034    */
1035 
1036   /* lock for good measure, since the caller had it */
1037   GST_STREAM_SYNCHRONIZER_LOCK (self);
1038 }
1039 
1040 static void
gst_stream_synchronizer_release_pad(GstElement * element,GstPad * pad)1041 gst_stream_synchronizer_release_pad (GstElement * element, GstPad * pad)
1042 {
1043   GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (element);
1044   GstSyncStream *stream;
1045 
1046   GST_STREAM_SYNCHRONIZER_LOCK (self);
1047   stream = gst_streamsync_pad_get_stream (pad);
1048   g_assert (stream->sinkpad == pad);
1049 
1050   gst_stream_synchronizer_release_stream (self, stream);
1051 
1052   gst_syncstream_unref (stream);
1053   GST_STREAM_SYNCHRONIZER_UNLOCK (self);
1054 }
1055 
1056 static GstStateChangeReturn
gst_stream_synchronizer_change_state(GstElement * element,GstStateChange transition)1057 gst_stream_synchronizer_change_state (GstElement * element,
1058     GstStateChange transition)
1059 {
1060   GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (element);
1061   GstStateChangeReturn ret;
1062 
1063   switch (transition) {
1064     case GST_STATE_CHANGE_NULL_TO_READY:
1065       GST_DEBUG_OBJECT (self, "State change NULL->READY");
1066       self->shutdown = FALSE;
1067       break;
1068     case GST_STATE_CHANGE_READY_TO_PAUSED:
1069       GST_DEBUG_OBJECT (self, "State change READY->PAUSED");
1070       self->group_start_time = 0;
1071       self->have_group_id = TRUE;
1072       self->group_id = G_MAXUINT;
1073       self->shutdown = FALSE;
1074       self->flushing = FALSE;
1075       self->eos = FALSE;
1076       break;
1077     case GST_STATE_CHANGE_PAUSED_TO_READY:{
1078       GList *l;
1079 
1080       GST_DEBUG_OBJECT (self, "State change PAUSED->READY");
1081 
1082       GST_STREAM_SYNCHRONIZER_LOCK (self);
1083       self->flushing = TRUE;
1084       self->shutdown = TRUE;
1085       for (l = self->streams; l; l = l->next) {
1086         GstSyncStream *ostream = l->data;
1087         g_cond_broadcast (&ostream->stream_finish_cond);
1088       }
1089       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
1090     }
1091     default:
1092       break;
1093   }
1094 
1095   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1096   GST_DEBUG_OBJECT (self, "Base class state changed returned: %d", ret);
1097   if (G_UNLIKELY (ret != GST_STATE_CHANGE_SUCCESS))
1098     return ret;
1099 
1100   switch (transition) {
1101     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:{
1102       GList *l;
1103 
1104       GST_DEBUG_OBJECT (self, "State change PLAYING->PAUSED");
1105 
1106       GST_STREAM_SYNCHRONIZER_LOCK (self);
1107       for (l = self->streams; l; l = l->next) {
1108         GstSyncStream *stream = l->data;
1109         /* send GAP event to sink to finished pre-roll. The reason is function
1110          * chain () will be blocked on pad_push (), so can't trigger the track
1111          * which reach EOS to send GAP event. */
1112         if (stream->is_eos && !stream->eos_sent) {
1113           stream->send_gap_event = TRUE;
1114           stream->gap_duration = GST_CLOCK_TIME_NONE;
1115           g_cond_broadcast (&stream->stream_finish_cond);
1116         }
1117       }
1118       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
1119       break;
1120     }
1121     case GST_STATE_CHANGE_PAUSED_TO_READY:{
1122       GList *l;
1123 
1124       GST_DEBUG_OBJECT (self, "State change PAUSED->READY");
1125       self->group_start_time = 0;
1126 
1127       GST_STREAM_SYNCHRONIZER_LOCK (self);
1128       for (l = self->streams; l; l = l->next) {
1129         GstSyncStream *stream = l->data;
1130 
1131         gst_segment_init (&stream->segment, GST_FORMAT_UNDEFINED);
1132         stream->gap_duration = GST_CLOCK_TIME_NONE;
1133         stream->wait = FALSE;
1134         stream->is_eos = FALSE;
1135         stream->eos_sent = FALSE;
1136         stream->flushing = FALSE;
1137         stream->send_gap_event = FALSE;
1138       }
1139       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
1140       break;
1141     }
1142     case GST_STATE_CHANGE_READY_TO_NULL:{
1143       GST_DEBUG_OBJECT (self, "State change READY->NULL");
1144 
1145       GST_STREAM_SYNCHRONIZER_LOCK (self);
1146       self->current_stream_number = 0;
1147       GST_STREAM_SYNCHRONIZER_UNLOCK (self);
1148       break;
1149     }
1150     default:
1151       break;
1152   }
1153 
1154   return ret;
1155 }
1156 
1157 /* GObject vfuncs */
1158 static void
gst_stream_synchronizer_finalize(GObject * object)1159 gst_stream_synchronizer_finalize (GObject * object)
1160 {
1161   GstStreamSynchronizer *self = GST_STREAM_SYNCHRONIZER (object);
1162 
1163   g_mutex_clear (&self->lock);
1164 
1165   G_OBJECT_CLASS (parent_class)->finalize (object);
1166 }
1167 
1168 /* GObject type initialization */
1169 static void
gst_stream_synchronizer_init(GstStreamSynchronizer * self)1170 gst_stream_synchronizer_init (GstStreamSynchronizer * self)
1171 {
1172   g_mutex_init (&self->lock);
1173 }
1174 
1175 static void
gst_stream_synchronizer_class_init(GstStreamSynchronizerClass * klass)1176 gst_stream_synchronizer_class_init (GstStreamSynchronizerClass * klass)
1177 {
1178   GObjectClass *gobject_class = (GObjectClass *) klass;
1179   GstElementClass *element_class = (GstElementClass *) klass;
1180 
1181   gobject_class->finalize = gst_stream_synchronizer_finalize;
1182 
1183   gst_element_class_add_static_pad_template (element_class, &srctemplate);
1184   gst_element_class_add_static_pad_template (element_class, &sinktemplate);
1185 
1186   gst_element_class_set_static_metadata (element_class,
1187       "Stream Synchronizer", "Generic",
1188       "Synchronizes a group of streams to have equal durations and starting points",
1189       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
1190 
1191   element_class->change_state =
1192       GST_DEBUG_FUNCPTR (gst_stream_synchronizer_change_state);
1193   element_class->request_new_pad =
1194       GST_DEBUG_FUNCPTR (gst_stream_synchronizer_request_new_pad);
1195   element_class->release_pad =
1196       GST_DEBUG_FUNCPTR (gst_stream_synchronizer_release_pad);
1197 
1198   GST_DEBUG_CATEGORY_INIT (stream_synchronizer_debug,
1199       "streamsynchronizer", 0, "Stream Synchronizer");
1200 }
1201