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