mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-15 22:01:27 +00:00
1751835838
Changing states up and down while buffers are being pushed is not a valid use case. If a pad is deactivated and reactivated during a buffer push it is racy with the check of pushed sticky events and the actual chainfunction call. As it might call the chain without noticing the peer pad lost its previous sticky events. https://bugzilla.gnome.org/show_bug.cgi?id=758340
427 lines
14 KiB
C
427 lines
14 KiB
C
/* GStreamer unit tests for the funnel
|
|
*
|
|
* Copyright (C) 2008 Collabora, Nokia
|
|
* @author: Olivier Crete <olivier.crete@collabora.co.uk>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <gst/check/gstharness.h>
|
|
#include <gst/check/gstcheck.h>
|
|
|
|
struct TestData
|
|
{
|
|
GstElement *funnel;
|
|
GstPad *funnelsrc, *funnelsink11, *funnelsink22;
|
|
GstPad *mysink, *mysrc1, *mysrc2;
|
|
GstCaps *mycaps;
|
|
};
|
|
|
|
static void
|
|
setup_test_objects (struct TestData *td, GstPadChainFunction chain_func)
|
|
{
|
|
td->mycaps = gst_caps_new_empty_simple ("test/test");
|
|
|
|
td->funnel = gst_element_factory_make ("funnel", NULL);
|
|
|
|
td->funnelsrc = gst_element_get_static_pad (td->funnel, "src");
|
|
fail_unless (td->funnelsrc != NULL);
|
|
|
|
td->funnelsink11 = gst_element_get_request_pad (td->funnel, "sink_11");
|
|
fail_unless (td->funnelsink11 != NULL);
|
|
fail_unless (!strcmp (GST_OBJECT_NAME (td->funnelsink11), "sink_11"));
|
|
|
|
td->funnelsink22 = gst_element_get_request_pad (td->funnel, "sink_22");
|
|
fail_unless (td->funnelsink22 != NULL);
|
|
fail_unless (!strcmp (GST_OBJECT_NAME (td->funnelsink22), "sink_22"));
|
|
|
|
fail_unless (gst_element_set_state (td->funnel, GST_STATE_PLAYING) ==
|
|
GST_STATE_CHANGE_SUCCESS);
|
|
|
|
td->mysink = gst_pad_new ("sink", GST_PAD_SINK);
|
|
gst_pad_set_chain_function (td->mysink, chain_func);
|
|
gst_pad_set_active (td->mysink, TRUE);
|
|
|
|
td->mysrc1 = gst_pad_new ("src1", GST_PAD_SRC);
|
|
gst_pad_set_active (td->mysrc1, TRUE);
|
|
gst_check_setup_events_with_stream_id (td->mysrc1, td->funnel, td->mycaps,
|
|
GST_FORMAT_BYTES, "test1");
|
|
|
|
td->mysrc2 = gst_pad_new ("src2", GST_PAD_SRC);
|
|
gst_pad_set_active (td->mysrc2, TRUE);
|
|
gst_check_setup_events_with_stream_id (td->mysrc2, td->funnel, td->mycaps,
|
|
GST_FORMAT_BYTES, "test2");
|
|
|
|
fail_unless (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (td->funnelsrc,
|
|
td->mysink)));
|
|
|
|
fail_unless (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (td->mysrc1,
|
|
td->funnelsink11)));
|
|
|
|
fail_unless (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (td->mysrc2,
|
|
td->funnelsink22)));
|
|
|
|
}
|
|
|
|
static void
|
|
release_test_objects (struct TestData *td)
|
|
{
|
|
gst_pad_set_active (td->mysink, FALSE);
|
|
gst_pad_set_active (td->mysrc1, FALSE);
|
|
gst_pad_set_active (td->mysrc1, FALSE);
|
|
|
|
gst_object_unref (td->mysink);
|
|
gst_object_unref (td->mysrc1);
|
|
gst_object_unref (td->mysrc2);
|
|
|
|
fail_unless (gst_element_set_state (td->funnel, GST_STATE_NULL) ==
|
|
GST_STATE_CHANGE_SUCCESS);
|
|
|
|
gst_object_unref (td->funnelsrc);
|
|
gst_element_release_request_pad (td->funnel, td->funnelsink11);
|
|
gst_object_unref (td->funnelsink11);
|
|
gst_element_release_request_pad (td->funnel, td->funnelsink22);
|
|
gst_object_unref (td->funnelsink22);
|
|
|
|
gst_caps_unref (td->mycaps);
|
|
gst_object_unref (td->funnel);
|
|
}
|
|
|
|
static gint bufcount = 0;
|
|
static gint alloccount = 0;
|
|
|
|
static GstFlowReturn
|
|
chain_ok (GstPad * pad, GstObject * parent, GstBuffer * buffer)
|
|
{
|
|
bufcount++;
|
|
|
|
gst_buffer_unref (buffer);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
GST_START_TEST (test_funnel_simple)
|
|
{
|
|
struct TestData td;
|
|
|
|
setup_test_objects (&td, chain_ok);
|
|
|
|
bufcount = 0;
|
|
alloccount = 0;
|
|
|
|
fail_unless (gst_pad_push (td.mysrc1, gst_buffer_new ()) == GST_FLOW_OK);
|
|
fail_unless (gst_pad_push (td.mysrc2, gst_buffer_new ()) == GST_FLOW_OK);
|
|
|
|
fail_unless (bufcount == 2);
|
|
|
|
release_test_objects (&td);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
guint num_eos = 0;
|
|
|
|
static gboolean
|
|
eos_event_func (GstPad * pad, GstObject * parent, GstEvent * event)
|
|
{
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS)
|
|
++num_eos;
|
|
|
|
return gst_pad_event_default (pad, parent, event);
|
|
}
|
|
|
|
GST_START_TEST (test_funnel_eos)
|
|
{
|
|
struct TestData td;
|
|
GstSegment segment;
|
|
|
|
setup_test_objects (&td, chain_ok);
|
|
|
|
num_eos = 0;
|
|
bufcount = 0;
|
|
|
|
gst_pad_set_event_function (td.mysink, eos_event_func);
|
|
|
|
fail_unless (gst_pad_push (td.mysrc1, gst_buffer_new ()) == GST_FLOW_OK);
|
|
fail_unless (gst_pad_push (td.mysrc2, gst_buffer_new ()) == GST_FLOW_OK);
|
|
|
|
fail_unless (bufcount == 2);
|
|
|
|
fail_unless (gst_pad_push_event (td.mysrc1, gst_event_new_eos ()));
|
|
fail_unless (num_eos == 0);
|
|
|
|
fail_unless (gst_pad_push (td.mysrc1, gst_buffer_new ()) == GST_FLOW_EOS);
|
|
fail_unless (gst_pad_push (td.mysrc2, gst_buffer_new ()) == GST_FLOW_OK);
|
|
|
|
fail_unless (bufcount == 3);
|
|
|
|
fail_unless (gst_pad_push_event (td.mysrc2, gst_event_new_eos ()));
|
|
fail_unless (num_eos == 1);
|
|
|
|
fail_unless (gst_pad_push (td.mysrc1, gst_buffer_new ()) == GST_FLOW_EOS);
|
|
fail_unless (gst_pad_push (td.mysrc2, gst_buffer_new ()) == GST_FLOW_EOS);
|
|
|
|
fail_unless (bufcount == 3);
|
|
|
|
fail_unless (gst_pad_push_event (td.mysrc1, gst_event_new_flush_start ()));
|
|
fail_unless (gst_pad_push_event (td.mysrc1, gst_event_new_flush_stop (TRUE)));
|
|
|
|
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
|
gst_pad_push_event (td.mysrc1, gst_event_new_segment (&segment));
|
|
gst_pad_push_event (td.mysrc2, gst_event_new_segment (&segment));
|
|
|
|
fail_unless (gst_pad_push (td.mysrc1, gst_buffer_new ()) == GST_FLOW_OK);
|
|
fail_unless (gst_pad_push (td.mysrc2, gst_buffer_new ()) == GST_FLOW_EOS);
|
|
|
|
fail_unless (bufcount == 4);
|
|
|
|
fail_unless (gst_pad_unlink (td.mysrc1, td.funnelsink11));
|
|
gst_element_release_request_pad (td.funnel, td.funnelsink11);
|
|
gst_object_unref (td.funnelsink11);
|
|
fail_unless (num_eos == 2);
|
|
|
|
td.funnelsink11 = gst_element_get_request_pad (td.funnel, "sink_11");
|
|
fail_unless (td.funnelsink11 != NULL);
|
|
fail_unless (!strcmp (GST_OBJECT_NAME (td.funnelsink11), "sink_11"));
|
|
|
|
fail_unless (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (td.mysrc1,
|
|
td.funnelsink11)));
|
|
|
|
/* This will fail because everything is EOS already */
|
|
fail_if (gst_pad_push_event (td.mysrc1, gst_event_new_eos ()));
|
|
fail_unless (num_eos == 2);
|
|
|
|
fail_unless (gst_pad_unlink (td.mysrc1, td.funnelsink11));
|
|
gst_element_release_request_pad (td.funnel, td.funnelsink11);
|
|
gst_object_unref (td.funnelsink11);
|
|
fail_unless (num_eos == 2);
|
|
|
|
/* send only eos to check, it handles empty streams */
|
|
td.funnelsink11 = gst_element_get_request_pad (td.funnel, "sink_11");
|
|
fail_unless (td.funnelsink11 != NULL);
|
|
fail_unless (!strcmp (GST_OBJECT_NAME (td.funnelsink11), "sink_11"));
|
|
|
|
fail_unless (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (td.mysrc1,
|
|
td.funnelsink11)));
|
|
|
|
fail_unless (gst_pad_push_event (td.mysrc1, gst_event_new_flush_start ()));
|
|
fail_unless (gst_pad_push_event (td.mysrc1, gst_event_new_flush_stop (TRUE)));
|
|
fail_unless (gst_pad_push_event (td.mysrc2, gst_event_new_flush_start ()));
|
|
fail_unless (gst_pad_push_event (td.mysrc2, gst_event_new_flush_stop (TRUE)));
|
|
|
|
fail_unless (gst_pad_push_event (td.mysrc1, gst_event_new_eos ()));
|
|
fail_unless (gst_pad_push_event (td.mysrc2, gst_event_new_eos ()));
|
|
fail_unless (num_eos == 3);
|
|
|
|
fail_unless (gst_pad_unlink (td.mysrc1, td.funnelsink11));
|
|
gst_element_release_request_pad (td.funnel, td.funnelsink11);
|
|
gst_object_unref (td.funnelsink11);
|
|
fail_unless (num_eos == 3);
|
|
|
|
td.funnelsink11 = gst_element_get_request_pad (td.funnel, "sink_11");
|
|
fail_unless (td.funnelsink11 != NULL);
|
|
fail_unless (!strcmp (GST_OBJECT_NAME (td.funnelsink11), "sink_11"));
|
|
|
|
release_test_objects (&td);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
guint nb_stream_start_event = 0;
|
|
guint nb_caps_event = 0;
|
|
guint nb_segment_event = 0;
|
|
guint nb_gap_event = 0;
|
|
|
|
static GstPadProbeReturn
|
|
event_counter (GstObject * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
|
|
|
|
fail_unless (event != NULL);
|
|
fail_unless (GST_IS_EVENT (event));
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_STREAM_START:
|
|
++nb_stream_start_event;
|
|
break;
|
|
case GST_EVENT_CAPS:
|
|
++nb_caps_event;
|
|
break;
|
|
case GST_EVENT_SEGMENT:
|
|
++nb_segment_event;
|
|
break;
|
|
case GST_EVENT_GAP:
|
|
++nb_gap_event;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
/*
|
|
* Push GAP events into funnel to forward sticky events.
|
|
* Funnel element shoud also treat GAP events likes buffers.
|
|
* For example, funnel can be used for internal subtitle with streamiddemux.
|
|
* +--------------------------------------------------------------------------+
|
|
* | playbin +--------------------------------+ |
|
|
* | +--------------+ +----------------+ | +------------+ playsink | |
|
|
* | | uridecodebin | | input-selector | | | video-sink | | |
|
|
* | | | +----------------+ | +------------+ | |
|
|
* | | | | | |
|
|
* | | | +----------------+ | +------------+ | |
|
|
* | | | | input-selector | | | audio-sink | | |
|
|
* | | | +----------------+ | +------------+ | |
|
|
* | | | | | |
|
|
* | | | +----------------+ | +---------------+ +----------+ | |
|
|
* | | | | funnel | | | streamiddemux | | appsink0 | | |
|
|
* | +--------------+ +----------------+ | +---------------+ +----------+ | |
|
|
* | | +----------+ | |
|
|
* | | | appsinkn | | |
|
|
* | | +----------+ | |
|
|
* | +--------------------------------+ |
|
|
* +--------------------------------------------------------------------------+
|
|
* If no data was received in funnel and then sticky events can be pending continuously.
|
|
* And streamiddemux only receive gap events continuously.
|
|
* Thus, pipeline can not be constructed completely.
|
|
* For support it, need to handle GAP events likes buffers.
|
|
*/
|
|
GST_START_TEST (test_funnel_gap_event)
|
|
{
|
|
struct TestData td;
|
|
guint probe = 0;
|
|
|
|
setup_test_objects (&td, chain_ok);
|
|
|
|
nb_stream_start_event = 0;
|
|
nb_caps_event = 0;
|
|
nb_segment_event = 0;
|
|
nb_gap_event = 0;
|
|
bufcount = 0;
|
|
|
|
probe = gst_pad_add_probe (td.mysink, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
|
|
(GstPadProbeCallback) event_counter, NULL, NULL);
|
|
|
|
/* push a gap event to srcpad1 to push sticky events */
|
|
fail_unless (gst_pad_push_event (td.mysrc1, gst_event_new_gap (0,
|
|
GST_SECOND)));
|
|
|
|
fail_unless (nb_stream_start_event == 1);
|
|
fail_unless (nb_caps_event == 1);
|
|
fail_unless (nb_segment_event == 1);
|
|
fail_unless (nb_gap_event == 1);
|
|
|
|
/* push a gap event to srcpad2 to push sticky events */
|
|
fail_unless (gst_pad_push_event (td.mysrc2, gst_event_new_gap (0,
|
|
GST_SECOND)));
|
|
|
|
fail_unless (nb_stream_start_event == 2);
|
|
fail_unless (nb_caps_event == 2);
|
|
fail_unless (nb_segment_event == 2);
|
|
fail_unless (nb_gap_event == 2);
|
|
|
|
/* push a gap event to srcpad2 */
|
|
fail_unless (gst_pad_push_event (td.mysrc2, gst_event_new_gap (0,
|
|
GST_SECOND)));
|
|
|
|
fail_unless (nb_stream_start_event == 2);
|
|
fail_unless (nb_caps_event == 2);
|
|
fail_unless (nb_segment_event == 2);
|
|
fail_unless (nb_gap_event == 3);
|
|
|
|
/* push a gap event to srcpad1 */
|
|
fail_unless (gst_pad_push_event (td.mysrc1, gst_event_new_gap (0,
|
|
GST_SECOND)));
|
|
|
|
fail_unless (nb_stream_start_event == 3);
|
|
fail_unless (nb_caps_event == 3);
|
|
fail_unless (nb_segment_event == 3);
|
|
fail_unless (nb_gap_event == 4);
|
|
|
|
/* push buffer */
|
|
fail_unless (gst_pad_push (td.mysrc1, gst_buffer_new ()) == GST_FLOW_OK);
|
|
fail_unless (gst_pad_push (td.mysrc2, gst_buffer_new ()) == GST_FLOW_OK);
|
|
|
|
fail_unless (nb_stream_start_event == 4);
|
|
fail_unless (nb_caps_event == 4);
|
|
fail_unless (nb_segment_event == 4);
|
|
fail_unless (nb_gap_event == 4);
|
|
fail_unless (bufcount == 2);
|
|
|
|
gst_pad_remove_probe (td.mysink, probe);
|
|
|
|
release_test_objects (&td);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_funnel_stress)
|
|
{
|
|
GstHarness *h0 = gst_harness_new_with_padnames ("funnel", "sink_0", "src");
|
|
GstHarness *h1 = gst_harness_new_with_element (h0->element, "sink_1", NULL);
|
|
GstHarnessThread *req, *push0, *push1;
|
|
GstPadTemplate *templ =
|
|
gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (h0->element),
|
|
"sink_%u");
|
|
GstCaps *caps = gst_caps_from_string ("testcaps");
|
|
GstBuffer *buf = gst_buffer_new ();
|
|
GstSegment segment;
|
|
|
|
gst_segment_init (&segment, GST_FORMAT_TIME);
|
|
|
|
req = gst_harness_stress_requestpad_start (h0, templ, NULL, NULL, TRUE);
|
|
push0 = gst_harness_stress_push_buffer_start (h0, caps, &segment, buf);
|
|
push1 = gst_harness_stress_push_buffer_start (h1, caps, &segment, buf);
|
|
|
|
gst_caps_unref (caps);
|
|
gst_buffer_unref (buf);
|
|
|
|
/* test-length */
|
|
g_usleep (G_USEC_PER_SEC * 1);
|
|
|
|
gst_harness_stress_thread_stop (push1);
|
|
gst_harness_stress_thread_stop (push0);
|
|
gst_harness_stress_thread_stop (req);
|
|
|
|
gst_harness_teardown (h1);
|
|
gst_harness_teardown (h0);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
|
|
static Suite *
|
|
funnel_suite (void)
|
|
{
|
|
Suite *s = suite_create ("funnel");
|
|
TCase *tc_chain;
|
|
|
|
tc_chain = tcase_create ("funnel simple");
|
|
tcase_add_test (tc_chain, test_funnel_simple);
|
|
tcase_add_test (tc_chain, test_funnel_eos);
|
|
tcase_add_test (tc_chain, test_funnel_gap_event);
|
|
tcase_add_test (tc_chain, test_funnel_stress);
|
|
suite_add_tcase (s, tc_chain);
|
|
|
|
return s;
|
|
}
|
|
|
|
GST_CHECK_MAIN (funnel);
|