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