1 /* Gnonlin
2  * Copyright (C) <2009> Alessandro Decina <alessandro.decina@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 #include "common.h"
20 
21 typedef struct
22 {
23   GstElement *composition;
24   GstElement *source3;
25 } TestClosure;
26 
27 static int composition_pad_added;
28 static int composition_pad_removed;
29 static int seek_events;
30 static gulong blockprobeid = 0;
31 static GMutex pad_added_lock;
32 static GCond pad_added_cond;
33 
34 static GstPadProbeReturn
on_source1_pad_event_cb(GstPad * pad,GstPadProbeInfo * info,gpointer user_data)35 on_source1_pad_event_cb (GstPad * pad, GstPadProbeInfo * info,
36     gpointer user_data)
37 {
38   if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEEK)
39     ++seek_events;
40 
41   return GST_PAD_PROBE_OK;
42 }
43 
44 static void
on_source1_pad_added_cb(GstElement * source,GstPad * pad,gpointer user_data)45 on_source1_pad_added_cb (GstElement * source, GstPad * pad, gpointer user_data)
46 {
47   gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
48       (GstPadProbeCallback) on_source1_pad_event_cb, NULL, NULL);
49 }
50 
51 static void
on_composition_pad_added_cb(GstElement * composition,GstPad * pad,GstElement * sink)52 on_composition_pad_added_cb (GstElement * composition, GstPad * pad,
53     GstElement * sink)
54 {
55   GstPad *s = gst_element_get_static_pad (sink, "sink");
56   gst_pad_link (pad, s);
57   ++composition_pad_added;
58   g_mutex_lock (&pad_added_lock);
59   g_cond_broadcast (&pad_added_cond);
60   g_mutex_unlock (&pad_added_lock);
61   gst_object_unref (s);
62 }
63 
64 static void
on_composition_pad_removed_cb(GstElement * composition,GstPad * pad,GstElement * sink)65 on_composition_pad_removed_cb (GstElement * composition, GstPad * pad,
66     GstElement * sink)
67 {
68   ++composition_pad_removed;
69 }
70 
GST_START_TEST(test_change_object_start_stop_in_current_stack)71 GST_START_TEST (test_change_object_start_stop_in_current_stack)
72 {
73   GstElement *pipeline;
74   GstElement *comp, *source1, *def, *sink;
75   GstBus *bus;
76   GstMessage *message;
77   gboolean carry_on, ret = FALSE;
78   int seek_events_before;
79 
80   pipeline = gst_pipeline_new ("test_pipeline");
81   comp =
82       gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
83 
84   sink = gst_element_factory_make_or_warn ("fakesink", "sink");
85   gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);
86 
87   /* connect to pad-added */
88   g_object_connect (comp, "signal::pad-added",
89       on_composition_pad_added_cb, sink, NULL);
90   g_object_connect (comp, "signal::pad-removed",
91       on_composition_pad_removed_cb, NULL, NULL);
92 
93   /*
94      source1
95      Start : 0s
96      Duration : 2s
97      Priority : 2
98    */
99 
100   source1 = videotest_gnl_src ("source1", 0, 2 * GST_SECOND, 2, 2);
101   g_object_connect (source1, "signal::pad-added",
102       on_source1_pad_added_cb, NULL, NULL);
103 
104   /*
105      def (default source)
106      Priority = G_MAXUINT32
107    */
108   def =
109       videotest_gnl_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, 2,
110       G_MAXUINT32);
111   g_object_set (def, "expandable", TRUE, NULL);
112 
113   ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
114   ASSERT_OBJECT_REFCOUNT (def, "default", 1);
115 
116   /* Add source 1 */
117 
118   /* keep an extra ref to source1 as we remove it from the bin */
119   gst_object_ref (source1);
120   gst_bin_add (GST_BIN (comp), source1);
121 
122   /* Add default */
123   gst_bin_add (GST_BIN (comp), def);
124   g_signal_emit_by_name (comp, "commit", TRUE, &ret);
125   check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND);
126   check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
127 
128   bus = gst_element_get_bus (GST_ELEMENT (pipeline));
129 
130   GST_DEBUG ("Setting pipeline to PLAYING");
131   ASSERT_OBJECT_REFCOUNT (source1, "source1", 2);
132 
133   fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
134           GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE);
135 
136   GST_DEBUG ("Let's poll the bus");
137 
138   carry_on = TRUE;
139   while (carry_on) {
140     message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
141     if (message) {
142       switch (GST_MESSAGE_TYPE (message)) {
143         case GST_MESSAGE_ASYNC_DONE:
144         {
145           carry_on = FALSE;
146           GST_DEBUG ("Pipeline reached PAUSED, stopping polling");
147           break;
148         }
149         case GST_MESSAGE_EOS:
150         {
151           GST_WARNING ("Saw EOS");
152 
153           fail_if (TRUE);
154         }
155         case GST_MESSAGE_ERROR:
156           fail_error_message (message);
157         default:
158           break;
159       }
160       gst_mini_object_unref (GST_MINI_OBJECT (message));
161     }
162   }
163 
164   fail_unless_equals_int (composition_pad_added, 1);
165   fail_unless_equals_int (composition_pad_removed, 0);
166 
167   seek_events_before = seek_events;
168 
169   /* pipeline is paused at this point */
170 
171   /* move source1 out of the active segment */
172   g_object_set (source1, "start", (guint64) 4 * GST_SECOND, NULL);
173   g_signal_emit_by_name (comp, "commit", TRUE, &ret);
174   fail_unless (seek_events > seek_events_before);
175 
176   /* remove source1 from the composition, which will become empty and remove the
177    * ghostpad */
178   gst_bin_remove (GST_BIN (comp), source1);
179 
180   fail_unless_equals_int (composition_pad_added, 1);
181   fail_unless_equals_int (composition_pad_removed, 1);
182 
183   g_object_set (source1, "start", (guint64) 0 * GST_SECOND, NULL);
184   /* add the source again and check that the ghostpad is added again */
185   gst_bin_add (GST_BIN (comp), source1);
186   g_signal_emit_by_name (comp, "commit", TRUE, &ret);
187 
188   g_mutex_lock (&pad_added_lock);
189   g_cond_wait (&pad_added_cond, &pad_added_lock);
190   fail_unless_equals_int (composition_pad_added, 2);
191   fail_unless_equals_int (composition_pad_removed, 1);
192   g_mutex_unlock (&pad_added_lock);
193 
194   seek_events_before = seek_events;
195 
196   g_object_set (source1, "duration", (guint64) 1 * GST_SECOND, NULL);
197   g_signal_emit_by_name (comp, "commit", TRUE, &ret);
198   fail_unless (seek_events > seek_events_before);
199 
200   GST_DEBUG ("Setting pipeline to NULL");
201 
202   fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
203           GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
204   gst_element_set_state (source1, GST_STATE_NULL);
205   gst_object_unref (source1);
206 
207   GST_DEBUG ("Resetted pipeline to READY");
208 
209   ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2);
210   gst_object_unref (pipeline);
211   ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2);
212   gst_object_unref (bus);
213 }
214 
215 GST_END_TEST;
216 
GST_START_TEST(test_remove_invalid_object)217 GST_START_TEST (test_remove_invalid_object)
218 {
219   GstBin *composition;
220   GstElement *source1, *source2;
221 
222   composition = GST_BIN (gst_element_factory_make ("gnlcomposition",
223           "composition"));
224   source1 = gst_element_factory_make ("gnlsource", "source1");
225   source2 = gst_element_factory_make ("gnlsource", "source2");
226 
227   gst_bin_add (composition, source1);
228   fail_if (gst_bin_remove (composition, source2));
229   fail_unless (gst_bin_remove (composition, source1));
230 
231   gst_object_unref (composition);
232   gst_object_unref (source2);
233 }
234 
235 GST_END_TEST;
236 
237 static GstPadProbeReturn
pad_block(GstPad * pad,GstPadProbeInfo * info,gpointer user_data)238 pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
239 {
240   GstPad *ghost;
241   GstBin *bin;
242 
243   bin = GST_BIN (user_data);
244 
245   GST_DEBUG_OBJECT (pad, "probe type:0x%x", GST_PAD_PROBE_INFO_TYPE (info));
246 
247   ghost = gst_ghost_pad_new ("src", pad);
248   gst_pad_set_active (ghost, TRUE);
249 
250   gst_element_add_pad (GST_ELEMENT (bin), ghost);
251 
252   return GST_PAD_PROBE_REMOVE;
253 }
254 
255 static void
no_more_pads_test_cb(GObject * object,TestClosure * c)256 no_more_pads_test_cb (GObject * object, TestClosure * c)
257 {
258   gboolean ret;
259 
260   GST_WARNING ("NO MORE PADS");
261   gst_bin_add (GST_BIN (c->composition), c->source3);
262   g_signal_emit_by_name (c->composition, "commit", TRUE, &ret);
263 }
264 
GST_START_TEST(test_no_more_pads_race)265 GST_START_TEST (test_no_more_pads_race)
266 {
267   gboolean ret;
268   GstElement *source1, *source2, *source3;
269   GstBin *bin;
270   GstElement *videotestsrc1, *videotestsrc2;
271   GstElement *operation;
272   GstElement *composition;
273   GstElement *videomixer, *fakesink;
274   GstElement *pipeline;
275   GstBus *bus;
276   GstMessage *message;
277   GstPad *pad;
278   TestClosure closure;
279 
280   /* We create a composition with an operation and three sources. The operation
281    * contains a videomixer instance and the three sources are videotestsrc's.
282    *
283    * One of the sources, source2, contains videotestsrc inside a bin. Initially
284    * the bin doesn't have a source pad. We do this to exercise the dynamic src
285    * pad code path in gnlcomposition. We block on the videotestsrc srcpad and in
286    * the pad block callback we ghost the pad and add the ghost to the parent
287    * bin. This makes gnlsource emit no-more-pads, which is used by
288    * gnlcomposition to link the source2:src pad to videomixer.
289    *
290    * We start with the composition containing operation and source1. We preroll
291    * and then add source2. Source2 will do what described above and emit
292    * no-more-pads. We connect to that no-more-pads and from there we add source3 to
293    * the composition. Adding a new source will make gnlcomposition deactivate
294    * the old stack and activate a new one. The new one contains operation,
295    * source1, source2 and source3. Source2 was active in the old stack as well and
296    * gnlcomposition is *still waiting* for no-more-pads to be emitted on it
297    * (since the no-more-pads emission is now blocked in our test's no-more-pads
298    * callback, calling gst_bin_add). In short, here, we're simulating a race between
299    * no-more-pads and someone modifying the composition.
300    *
301    * Activating the new stack, gnlcomposition calls compare_relink_single_node,
302    * which finds an existing source pad for source2 this time since we have
303    * already blocked and ghosted. It takes another code path that assumes that
304    * source2 doesn't have dynamic pads and *BOOM*.
305    */
306 
307   pipeline = GST_ELEMENT (gst_pipeline_new (NULL));
308   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
309 
310   composition = gst_element_factory_make ("gnlcomposition", "composition");
311   fakesink = gst_element_factory_make ("fakesink", NULL);
312   fail_unless (fakesink != NULL);
313   g_object_set (fakesink, "sync", TRUE, NULL);
314 
315   /* operation */
316   operation = gst_element_factory_make ("gnloperation", "operation");
317   videomixer = gst_element_factory_make ("videomixer", "videomixer");
318   fail_unless (videomixer != NULL);
319   gst_bin_add (GST_BIN (operation), videomixer);
320   g_object_set (operation, "start", (guint64) 0 * GST_SECOND,
321       "duration", (guint64) 10 * GST_SECOND,
322       "inpoint", (guint64) 0 * GST_SECOND, "priority", 10, NULL);
323   gst_bin_add (GST_BIN (composition), operation);
324 
325   /* source 1 */
326   source1 = gst_element_factory_make ("gnlsource", "source1");
327   videotestsrc1 = gst_element_factory_make ("videotestsrc", "videotestsrc1");
328   gst_bin_add (GST_BIN (source1), videotestsrc1);
329   g_object_set (source1, "start", (guint64) 0 * GST_SECOND, "duration",
330       (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority",
331       20, NULL);
332 
333   /* source2 */
334   source2 = gst_element_factory_make ("gnlsource", "source2");
335   bin = GST_BIN (gst_bin_new (NULL));
336   videotestsrc2 = gst_element_factory_make ("videotestsrc", "videotestsrc2");
337   pad = gst_element_get_static_pad (videotestsrc2, "src");
338   blockprobeid =
339       gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
340       (GstPadProbeCallback) pad_block, bin, NULL);
341   gst_bin_add (bin, videotestsrc2);
342   gst_bin_add (GST_BIN (source2), GST_ELEMENT (bin));
343   g_object_set (source2, "start", (guint64) 0 * GST_SECOND, "duration",
344       (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority",
345       20, NULL);
346 
347   /* source3 */
348   source3 = gst_element_factory_make ("gnlsource", "source3");
349   videotestsrc2 = gst_element_factory_make ("videotestsrc", "videotestsrc3");
350   gst_bin_add (GST_BIN (source3), videotestsrc2);
351   g_object_set (source3, "start", (guint64) 0 * GST_SECOND, "duration",
352       (guint64) 5 * GST_SECOND, "inpoint", (guint64) 0 * GST_SECOND, "priority",
353       20, NULL);
354 
355   closure.composition = composition;
356   closure.source3 = source3;
357   g_object_connect (source2, "signal::no-more-pads",
358       no_more_pads_test_cb, &closure, NULL);
359 
360   gst_bin_add (GST_BIN (composition), source1);
361   g_signal_emit_by_name (composition, "commit", TRUE, &ret);
362   g_object_connect (composition, "signal::pad-added",
363       on_composition_pad_added_cb, fakesink, NULL);
364   g_object_connect (composition, "signal::pad-removed",
365       on_composition_pad_removed_cb, NULL, NULL);
366 
367   GST_DEBUG ("Adding composition to pipeline");
368 
369   gst_bin_add_many (GST_BIN (pipeline), composition, fakesink, NULL);
370 
371   GST_DEBUG ("Setting pipeline to PAUSED");
372 
373   fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED)
374       == GST_STATE_CHANGE_FAILURE);
375 
376   message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
377       GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR);
378   if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
379     fail_error_message (message);
380   }
381   gst_message_unref (message);
382 
383   GST_DEBUG ("Adding second source");
384 
385   /* FIXME: maybe slow down the videotestsrc steaming thread */
386   gst_bin_add (GST_BIN (composition), source2);
387   g_signal_emit_by_name (composition, "commit", TRUE, &ret);
388 
389   message =
390       gst_bus_timed_pop_filtered (bus, GST_SECOND / 10, GST_MESSAGE_ERROR);
391   if (message) {
392     if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
393       fail_error_message (message);
394     } else {
395       fail_if (TRUE);
396     }
397 
398     gst_message_unref (message);
399   }
400 
401   gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
402   gst_object_unref (pipeline);
403   gst_object_unref (bus);
404 }
405 
406 GST_END_TEST;
407 
GST_START_TEST(test_simple_adder)408 GST_START_TEST (test_simple_adder)
409 {
410   GstBus *bus;
411   GstMessage *message;
412   GstElement *pipeline;
413   GstElement *gnl_adder;
414   GstElement *composition;
415   GstElement *adder, *fakesink;
416   GstClockTime start_playing_time;
417   GstElement *gnlsource1, *gnlsource2;
418   GstElement *audiotestsrc1, *audiotestsrc2;
419 
420   gboolean carry_on = TRUE, ret;
421   GstClockTime total_time = 10 * GST_SECOND;
422 
423   pipeline = GST_ELEMENT (gst_pipeline_new (NULL));
424   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
425 
426   composition = gst_element_factory_make ("gnlcomposition", "composition");
427   fakesink = gst_element_factory_make ("fakesink", NULL);
428   g_object_set (fakesink, "sync", TRUE, NULL);
429 
430   /* gnl_adder */
431   gnl_adder = gst_element_factory_make ("gnloperation", "gnl_adder");
432   adder = gst_element_factory_make ("adder", "adder");
433   fail_unless (adder != NULL);
434   gst_bin_add (GST_BIN (gnl_adder), adder);
435   g_object_set (gnl_adder, "start", (guint64) 0 * GST_SECOND,
436       "duration", total_time, "inpoint", (guint64) 0 * GST_SECOND,
437       "priority", 0, NULL);
438   gst_bin_add (GST_BIN (composition), gnl_adder);
439 
440   /* source 1 */
441   gnlsource1 = gst_element_factory_make ("gnlsource", "gnlsource1");
442   audiotestsrc1 = gst_element_factory_make ("audiotestsrc", "audiotestsrc1");
443   gst_bin_add (GST_BIN (gnlsource1), audiotestsrc1);
444   g_object_set (gnlsource1, "start", (guint64) 0 * GST_SECOND,
445       "duration", total_time / 2, "inpoint", (guint64) 0, "priority", 1, NULL);
446   fail_unless (gst_bin_add (GST_BIN (composition), gnlsource1));
447 
448   /* gnlsource2 */
449   gnlsource2 = gst_element_factory_make ("gnlsource", "gnlsource2");
450   audiotestsrc2 = gst_element_factory_make ("audiotestsrc", "audiotestsrc2");
451   gst_bin_add (GST_BIN (gnlsource2), GST_ELEMENT (audiotestsrc2));
452   g_object_set (gnlsource2, "start", (guint64) 0 * GST_SECOND,
453       "duration", total_time, "inpoint", (guint64) 0 * GST_SECOND, "priority",
454       2, NULL);
455   fail_unless (gst_bin_add (GST_BIN (composition), gnlsource2));
456 
457   /* Connecting signals */
458   g_object_connect (composition, "signal::pad-added",
459       on_composition_pad_added_cb, fakesink, NULL);
460   g_object_connect (composition, "signal::pad-removed",
461       on_composition_pad_removed_cb, NULL, NULL);
462 
463 
464   GST_DEBUG ("Adding composition to pipeline");
465 
466   gst_bin_add_many (GST_BIN (pipeline), composition, fakesink, NULL);
467 
468   GST_DEBUG ("Setting pipeline to PAUSED");
469 
470   g_signal_emit_by_name (composition, "commit", TRUE, &ret);
471   fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING)
472       == GST_STATE_CHANGE_FAILURE);
473 
474   message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
475       GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR);
476 
477   if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR)
478     fail_error_message (message);
479 
480   GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
481       GST_DEBUG_GRAPH_SHOW_ALL, "gnl-simple-adder-test-play");
482 
483   /* Now play the 10 second composition */
484   start_playing_time = gst_util_get_timestamp ();
485   while (carry_on) {
486 
487     if (GST_CLOCK_DIFF (start_playing_time, gst_util_get_timestamp ()) >
488         total_time + GST_SECOND) {
489       GST_ERROR ("No EOS found after %" GST_TIME_FORMAT " sec",
490           GST_TIME_ARGS ((total_time / GST_SECOND) + 1));
491       GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
492           GST_DEBUG_GRAPH_SHOW_ALL, "gnl-simple-adder-test-fail");
493 
494       fail_unless ("No EOS received" == NULL);
495 
496       break;
497     }
498 
499     message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10);
500     GST_LOG ("poll: %" GST_PTR_FORMAT, message);
501     if (message) {
502       switch (GST_MESSAGE_TYPE (message)) {
503         case GST_MESSAGE_EOS:
504           /* we should check if we really finished here */
505           GST_WARNING ("Got an EOS");
506           carry_on = FALSE;
507           break;
508         case GST_MESSAGE_SEGMENT_START:
509         case GST_MESSAGE_SEGMENT_DONE:
510           /* We shouldn't see any segement messages, since we didn't do a segment seek */
511           GST_WARNING ("Saw a Segment start/stop");
512           fail_if (TRUE);
513           carry_on = FALSE;
514           break;
515         case GST_MESSAGE_ERROR:
516           fail_error_message (message);
517         default:
518           break;
519       }
520       gst_mini_object_unref (GST_MINI_OBJECT (message));
521     }
522   }
523 
524   gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
525   gst_object_unref (pipeline);
526   gst_object_unref (bus);
527 }
528 
529 GST_END_TEST;
530 
531 static Suite *
gnonlin_suite(void)532 gnonlin_suite (void)
533 {
534   Suite *s = suite_create ("gnlcomposition");
535   TCase *tc_chain = tcase_create ("gnlcomposition");
536 
537   suite_add_tcase (s, tc_chain);
538 
539   g_cond_init (&pad_added_cond);
540   g_mutex_init (&pad_added_lock);
541   tcase_add_test (tc_chain, test_change_object_start_stop_in_current_stack);
542   tcase_add_test (tc_chain, test_remove_invalid_object);
543   if (gst_registry_check_feature_version (gst_registry_get (), "videomixer", 0,
544           11, 0)) {
545     tcase_add_test (tc_chain, test_no_more_pads_race);
546   } else {
547     GST_WARNING ("videomixer element not available, skipping 1 test");
548   }
549 
550   if (gst_registry_check_feature_version (gst_registry_get (), "adder", 1,
551           0, 0)) {
552     tcase_add_test (tc_chain, test_simple_adder);
553   } else {
554     GST_WARNING ("adder element not available, skipping 1 test");
555   }
556 
557   return s;
558 }
559 
560 GST_CHECK_MAIN (gnonlin)
561