From 66e25da31327704cece47a8aa7b2819b609703ad Mon Sep 17 00:00:00 2001 From: Havard Graff Date: Mon, 16 Dec 2013 10:47:47 +0100 Subject: [PATCH] check: Add GstHarness convenience API for unit tests http://gstconf.ubicast.tv/videos/gstharness-again-a-follow-up/ https://bugzilla.gnome.org/show_bug.cgi?id=751916 --- libs/gst/check/Makefile.am | 71 + libs/gst/check/gstharness.c | 2885 +++++++++++++++++++++++++++++++++++ libs/gst/check/gstharness.h | 197 +++ 3 files changed, 3153 insertions(+) create mode 100644 libs/gst/check/gstharness.c create mode 100644 libs/gst/check/gstharness.h diff --git a/libs/gst/check/Makefile.am b/libs/gst/check/Makefile.am index d80e3fd599..a08c4591f8 100644 --- a/libs/gst/check/Makefile.am +++ b/libs/gst/check/Makefile.am @@ -9,6 +9,7 @@ libgstcheck_@GST_API_VERSION@_la_SOURCES = \ gstbufferstraw.c \ gstcheck.c \ gstconsistencychecker.c \ + gstharness.c \ gsttestclock.c libgstcheck_@GST_API_VERSION@_la_CFLAGS = $(GST_OBJ_CFLAGS) \ @@ -29,6 +30,7 @@ libgstcheck_@GST_API_VERSION@include_HEADERS = \ gstbufferstraw.h \ gstcheck.h \ gstconsistencychecker.h \ + gstharness.h \ gsttestclock.h nodist_libgstcheck_@GST_API_VERSION@include_HEADERS = \ @@ -93,6 +95,75 @@ LIBGSTCHECK_EXPORTED_FUNCS = \ gst_consistency_checker_new \ gst_consistency_checker_reset \ gst_consistency_checker_free \ + gst_harness_add_element_src_pad \ + gst_harness_add_probe \ + gst_harness_add_sink \ + gst_harness_add_sink_parse \ + gst_harness_add_src \ + gst_harness_add_src_parse \ + gst_harness_buffers_received \ + gst_harness_buffers_in_queue \ + gst_harness_crank_multiple_clock_waits \ + gst_harness_crank_single_clock_wait \ + gst_harness_create_buffer \ + gst_harness_dump_to_file \ + gst_harness_events_received \ + gst_harness_events_in_queue \ + gst_harness_find_element \ + gst_harness_get \ + gst_harness_get_allocator \ + gst_harness_get_last_pushed_timestamp \ + gst_harness_get_testclock \ + gst_harness_new \ + gst_harness_new_full \ + gst_harness_new_parse \ + gst_harness_new_with_element \ + gst_harness_new_with_padnames \ + gst_harness_new_with_templates \ + gst_harness_play \ + gst_harness_pull \ + gst_harness_pull_event \ + gst_harness_pull_upstream_event \ + gst_harness_push \ + gst_harness_push_and_pull \ + gst_harness_push_event \ + gst_harness_push_from_src \ + gst_harness_push_to_sink \ + gst_harness_query_latency \ + gst_harness_push_upstream_event \ + gst_harness_set \ + gst_harness_set_caps \ + gst_harness_set_caps_str \ + gst_harness_set_drop_buffers \ + gst_harness_set_blocking_push_mode \ + gst_harness_set_propose_allocator \ + gst_harness_set_sink_caps \ + gst_harness_set_src_caps \ + gst_harness_set_src_caps_str \ + gst_harness_set_sink_caps_str \ + gst_harness_set_time \ + gst_harness_set_upstream_latency \ + gst_harness_sink_push_many \ + gst_harness_src_crank_and_push_many \ + gst_harness_src_push_event \ + gst_harness_stress_custom_start \ + gst_harness_stress_property_start_full \ + gst_harness_stress_push_buffer_start_full \ + gst_harness_stress_push_buffer_with_cb_start_full \ + gst_harness_stress_push_event_start_full \ + gst_harness_stress_push_upstream_event_start_full \ + gst_harness_stress_requestpad_start_full \ + gst_harness_stress_statechange_start_full \ + gst_harness_stress_thread_stop \ + gst_harness_teardown \ + gst_harness_try_pull \ + gst_harness_try_pull_event \ + gst_harness_try_pull_upstream_event \ + gst_harness_upstream_events_received \ + gst_harness_upstream_events_in_queue \ + gst_harness_use_systemclock \ + gst_harness_use_testclock \ + gst_harness_wait_for_clock_id_waits \ gst_test_clock_get_type \ gst_test_clock_new \ gst_test_clock_new_with_start_time \ diff --git a/libs/gst/check/gstharness.c b/libs/gst/check/gstharness.c new file mode 100644 index 0000000000..2f7ce2813e --- /dev/null +++ b/libs/gst/check/gstharness.c @@ -0,0 +1,2885 @@ +/* GstHarness - A test-harness for GStreamer testing + * + * Copyright (C) 2012-2015 Pexip + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gstharness + * @short_description: A test-harness for writing GStreamer unit tests + * @see_also: #GstTestClock,\ + * + * #GstHarness is ment to make writing unit test for GStreamer much easier. + * It can be though of as a way of treating a #GstElement as a black box, + * deterministially feeding it data, and controlling what data it outputs. + * + * The basic structure of #GstHarness is two "floating" #GstPads, that connects + * to the harnessed #GstElement src and sink #GstPads like so: + * + * __________________________ + * _____ | _____ _____ | _____ + * | | | | | | | | | | + * | src |--+-| sink| Element | src |-+--| sink| + * |_____| | |_____| |_____| | |_____| + * |__________________________| + * + * + * With this, you can now simulate any environment the #GstElement might find + * itself in. By specifying the #GstCaps of the harness #GstPads, using + * functions like gst_harness_set_src_caps or gst_harness_set_sink_caps_str, + * you can test how the #GstElement interacts with different capssets. + * + * Your harnessed #GstElement can of course also be a bin, and using + * gst_harness_new_parse supporting standard gst-launch syntax, you can + * easily test a whole pipeline instead of just one element. + * + * You can then go on to push #GstBuffers and #GstEvents on to the srcpad, + * using functions like gst_harness_push and gst_harness_push_event, and + * then pull them out to examine them with gst_harness_pull and + * gst_harness_pull_event. + * + * + * A simple buffer-in buffer-out example + * + * #include <gst/gst.h> + * #include <gst/check/gstharness.h> + * GstHarness *h; + * GstBuffer *in_buf; + * GstBuffer *out_buf; + * + * // attach the harness to the src and sink pad of GstQueue + * h = gst_harness_new ("queue"); + * + * // we must specify a caps before pushing buffers + * gst_harness_set_src_caps_str (h, "mycaps"); + * + * // create a buffer of size 42 + * in_buf = gst_harness_create_buffer (h, 42); + * + * // push the buffer into the queue + * gst_harness_push (h, in_buf); + * + * // pull the buffer from the queue + * out_buf = gst_harness_pull (h); + * + * // validate the buffer in is the same as buffer out + * fail_unless (in_buf == out_buf); + * + * // cleanup + * gst_buffer_unref (out_buf); + * gst_harness_teardown (h); + * + * + * + * + * Another main feature of the #GstHarness is its integration with the + * #GstTestClock. Operating the #GstTestClock can be very challenging, but + * #GstHarness simplifies some of the most desired actions a lot, like wanting + * to manually advance the clock while at the same time releasing a #GstClockID + * that is waiting, with functions like gst_harness_crank_single_clock_wait. + * + * #GstHarness also supports sub-harnesses, as a way of generating and + * validating data. A sub-harness is another #GstHarness that is managed by + * the "parent" harness, and can either be created by using the standard + * gst_harness_new type functions directly on the (GstHarness *)->src_harness, + * or using the much more convenient gst_harness_add_src or + * gst_harness_add_sink_parse. If you have a decoder-element you want to test, + * (like vp8dec) it can be very useful to add a src-harness with both a + * src-element (videotestsrc) and an encoder (vp8enc) to feed the decoder data + * with different configurations, by simply doing: + * + * + * + * GstHarness * h = gst_harness_new (h, "vp8dec"); + * gst_harness_add_src_parse (h, "videotestsrc is-live=1 ! vp8enc", TRUE); + * + * + * + * and then feeding it data with: + * + * + * + * gst_harness_push_from_src (h); + * + * + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstharness.h" + +#include +#include +#include + +static void gst_harness_stress_free (GstHarnessThread * t); + +#define HARNESS_KEY "harness" +#define HARNESS_REF "harness-ref" + +static GstStaticPadTemplate hsrctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); +static GstStaticPadTemplate hsinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +struct _GstHarnessPrivate { + gchar * element_sinkpad_name; + gchar * element_srcpad_name; + + GstCaps * src_caps; + GstCaps * sink_caps; + GstPad * sink_forward_pad; + + volatile gint recv_buffers; + volatile gint recv_events; + volatile gint recv_upstream_events; + + GAsyncQueue * buffer_queue; + GAsyncQueue * src_event_queue; + GAsyncQueue * sink_event_queue; + + GstClockTime latency_min; + GstClockTime latency_max; + gboolean has_clock_wait; + gboolean drop_buffers; + GstClockTime last_push_ts; + + GstBufferPool * pool; + GstAllocator * allocator; + GstAllocationParams allocation_params; + GstAllocator * propose_allocator; + GstAllocationParams propose_allocation_params; + + gboolean blocking_push_mode; + GCond blocking_push_cond; + GMutex blocking_push_mutex; + + GPtrArray * stress; +}; + +static GstFlowReturn +gst_harness_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY); + GstHarnessPrivate *priv = h->priv; + (void) parent; + g_assert (h != NULL); + g_mutex_lock (&priv->blocking_push_mutex); + g_atomic_int_inc (&priv->recv_buffers); + + if (priv->drop_buffers) + gst_buffer_unref (buffer); + else + g_async_queue_push (priv->buffer_queue, buffer); + + if (priv->blocking_push_mode) { + g_cond_wait (&priv->blocking_push_cond, &priv->blocking_push_mutex); + } + g_mutex_unlock (&priv->blocking_push_mutex); + + return GST_FLOW_OK; +} + +static gboolean +gst_harness_src_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY); + GstHarnessPrivate *priv = h->priv; + (void) parent; + g_assert (h != NULL); + g_atomic_int_inc (&priv->recv_upstream_events); + g_async_queue_push (priv->src_event_queue, event); + return TRUE; +} + +static gboolean +gst_harness_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY); + GstHarnessPrivate *priv = h->priv; + gboolean forward; + + g_assert (h != NULL); + (void) parent; + g_atomic_int_inc (&priv->recv_events); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_STREAM_START: + case GST_EVENT_CAPS: + case GST_EVENT_SEGMENT: + forward = TRUE; + break; + default: + forward = FALSE; + break; + } + + if (forward && priv->sink_forward_pad) { + gst_pad_push_event (priv->sink_forward_pad, event); + } else { + g_async_queue_push (priv->sink_event_queue, event); + } + + return TRUE; +} + +static void +gst_harness_decide_allocation (GstHarness * h, GstCaps * caps) +{ + GstHarnessPrivate *priv = h->priv; + GstQuery *query; + GstAllocator *allocator; + GstAllocationParams params; + GstBufferPool *pool = NULL; + guint size, min, max; + + query = gst_query_new_allocation (caps, FALSE); + gst_pad_peer_query (h->srcpad, query); + + if (gst_query_get_n_allocation_params (query) > 0) { + gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); + } else { + allocator = NULL; + gst_allocation_params_init (¶ms); + } + + if (gst_query_get_n_allocation_pools (query) > 0) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); +#if 0 + /* Most elements create their own pools if pool == NULL. Not sure if we + * want to do that in the harness since we may want to test the pool + * implementation of the elements. Not creating a pool will however ignore + * the returned size. */ + if (pool == NULL) + pool = gst_buffer_pool_new (); +#endif + } else { + pool = NULL; + size = min = max = 0; + } + gst_query_unref (query); + + if (pool) { + GstStructure *config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_set_params (config, caps, size, min, max); + gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); + gst_buffer_pool_set_config (pool, config); + } + + if (pool != priv->pool) { + if (priv->pool != NULL) + gst_buffer_pool_set_active (priv->pool, FALSE); + if (pool) + gst_buffer_pool_set_active (pool, TRUE); + } + + priv->allocation_params = params; + if (priv->allocator) + gst_object_unref (priv->allocator); + priv->allocator = allocator; + if (priv->pool) + gst_object_unref (priv->pool); + priv->pool = pool; +} + +static void +gst_harness_negotiate (GstHarness * h) +{ + GstCaps *caps; + + caps = gst_pad_get_current_caps (h->srcpad); + if (caps != NULL) { + gst_harness_decide_allocation (h, caps); + gst_caps_unref (caps); + } else { + GST_FIXME_OBJECT (h, "Cannot negotiate allocation because caps is not set"); + } +} + +static gboolean +gst_harness_sink_query (GstPad * pad, GstObject * parent, GstQuery * query) +{ + GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY); + GstHarnessPrivate *priv = h->priv; + gboolean res = TRUE; + g_assert (h != NULL); + + // FIXME: forward all queries? + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + gst_query_set_latency (query, TRUE, priv->latency_min, priv->latency_max); + break; + case GST_QUERY_CAPS: + { + GstCaps *caps, *filter = NULL; + + caps = priv->sink_caps ? gst_caps_ref (priv->sink_caps) : gst_caps_new_any (); + + gst_query_parse_caps (query, &filter); + if (filter != NULL) { + gst_caps_take (&caps, + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST)); + } + + gst_query_set_caps_result (query, caps); + gst_caps_unref (caps); + } + break; + case GST_QUERY_ALLOCATION: + { + if (priv->sink_forward_pad != NULL) { + GstPad *peer = gst_pad_get_peer (priv->sink_forward_pad); + g_assert (peer != NULL); + res = gst_pad_query (peer, query); + gst_object_unref (peer); + } else { + GstCaps *caps; + gboolean need_pool; + + gst_query_parse_allocation (query, &caps, &need_pool); + + /* FIXME: Can this be removed? */ + g_assert_cmpuint (0, ==, gst_query_get_n_allocation_params (query)); + gst_query_add_allocation_param (query, + priv->propose_allocator, &priv->propose_allocation_params); + + GST_DEBUG_OBJECT (pad, "proposing allocation %" GST_PTR_FORMAT, + priv->propose_allocator); + } + break; + } + default: + res = gst_pad_query_default (pad, parent, query); + } + + return res; +} + +static gboolean +gst_harness_src_query (GstPad * pad, GstObject * parent, GstQuery * query) +{ + GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY); + GstHarnessPrivate *priv = h->priv; + gboolean res = TRUE; + g_assert (h != NULL); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + gst_query_set_latency (query, TRUE, priv->latency_min, priv->latency_max); + break; + case GST_QUERY_CAPS: + { + GstCaps *caps, *filter = NULL; + + caps = priv->src_caps ? gst_caps_ref (priv->src_caps) : gst_caps_new_any (); + + gst_query_parse_caps (query, &filter); + if (filter != NULL) { + gst_caps_take (&caps, + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST)); + } + + gst_query_set_caps_result (query, caps); + gst_caps_unref (caps); + } + break; + default: + res = gst_pad_query_default (pad, parent, query); + } + return res; +} + +static void +gst_harness_element_ref (GstHarness * h) +{ + guint *data = g_object_get_data (G_OBJECT (h->element), HARNESS_REF); + if (data == NULL) { + data = g_new0 (guint, 1); + *data = 1; + g_object_set_data_full (G_OBJECT (h->element), HARNESS_REF, data, g_free); + } else { + (*data)++; + } +} + +static guint +gst_harness_element_unref (GstHarness * h) +{ + guint *data = g_object_get_data (G_OBJECT (h->element), HARNESS_REF); + g_assert (data != NULL); + (*data)--; + return *data; +} + +static void +gst_harness_link_element_srcpad (GstHarness * h, + const gchar * element_srcpad_name) +{ + GstHarnessPrivate *priv = h->priv; + GstPad *srcpad = gst_element_get_static_pad (h->element, + element_srcpad_name); + if (srcpad == NULL) + srcpad = gst_element_get_request_pad (h->element, element_srcpad_name); + g_assert (srcpad); + g_assert_cmpint (gst_pad_link (srcpad, h->sinkpad), ==, GST_PAD_LINK_OK); + g_free (priv->element_srcpad_name); + priv->element_srcpad_name = gst_pad_get_name (srcpad); + + gst_object_unref (srcpad); +} + +static void +gst_harness_link_element_sinkpad (GstHarness * h, + const gchar * element_sinkpad_name) +{ + GstHarnessPrivate *priv = h->priv; + GstPad *sinkpad = gst_element_get_static_pad (h->element, + element_sinkpad_name); + if (sinkpad == NULL) + sinkpad = gst_element_get_request_pad (h->element, element_sinkpad_name); + g_assert (sinkpad); + g_assert_cmpint (gst_pad_link (h->srcpad, sinkpad), ==, GST_PAD_LINK_OK); + g_free (priv->element_sinkpad_name); + priv->element_sinkpad_name = gst_pad_get_name (sinkpad); + + gst_object_unref (sinkpad); +} + +static void +gst_harness_setup_src_pad (GstHarness * h, + GstStaticPadTemplate * src_tmpl, const gchar * element_sinkpad_name) +{ + GstHarnessPrivate *priv = h->priv; + g_assert (src_tmpl); + + priv->src_event_queue = + g_async_queue_new_full ((GDestroyNotify) gst_event_unref); + + /* sending pad */ + h->srcpad = gst_pad_new_from_static_template (src_tmpl, "src"); + g_assert (h->srcpad); + g_object_set_data (G_OBJECT (h->srcpad), HARNESS_KEY, h); + + gst_pad_set_query_function (h->srcpad, gst_harness_src_query); + gst_pad_set_event_function (h->srcpad, gst_harness_src_event); + + gst_pad_set_active (h->srcpad, TRUE); + + if (element_sinkpad_name) + gst_harness_link_element_sinkpad (h, element_sinkpad_name); +} + +static void +gst_harness_setup_sink_pad (GstHarness * h, + GstStaticPadTemplate * sink_tmpl, const gchar * element_srcpad_name) +{ + GstHarnessPrivate *priv = h->priv; + g_assert (sink_tmpl); + + priv->buffer_queue = g_async_queue_new_full ( + (GDestroyNotify) gst_buffer_unref); + priv->sink_event_queue = g_async_queue_new_full ( + (GDestroyNotify) gst_event_unref); + + /* receiving pad */ + h->sinkpad = gst_pad_new_from_static_template (sink_tmpl, "sink"); + g_assert (h->sinkpad); + g_object_set_data (G_OBJECT (h->sinkpad), HARNESS_KEY, h); + + gst_pad_set_chain_function (h->sinkpad, gst_harness_chain); + gst_pad_set_query_function (h->sinkpad, gst_harness_sink_query); + gst_pad_set_event_function (h->sinkpad, gst_harness_sink_event); + + gst_pad_set_active (h->sinkpad, TRUE); + + if (element_srcpad_name) + gst_harness_link_element_srcpad (h, element_srcpad_name); +} + +static void +turn_async_and_sync_off (GstElement * element) +{ + GObjectClass *class = G_OBJECT_GET_CLASS (element); + if (g_object_class_find_property (class, "async")) + g_object_set (element, "async", FALSE, NULL); + if (g_object_class_find_property (class, "sync")) + g_object_set (element, "sync", FALSE, NULL); +} + +static gboolean +gst_pad_is_request_pad (GstPad * pad) +{ + GstPadTemplate *temp; + if (pad == NULL) + return FALSE; + temp = gst_pad_get_pad_template (pad); + if (temp == NULL) + return FALSE; + return GST_PAD_TEMPLATE_PRESENCE (temp) == GST_PAD_REQUEST; +} + +/** + * gst_harness_new_full: (skip) + * @element: a #GstElement to attach the harness to (transfer none) + * @hsrc: (allow-none): a #GstStaticPadTemplate describing the harness srcpad. + * %NULL will not create a harness srcpad. + * @sinkpad: (allow-none): a #gchar with the name of the element sinkpad that is + * then linked to the harness srcpad. Can be a static or request or a sometimes + * pad that has been added. %NULL will not get/request a sinkpad from the + * element. (Like if the element is a src) + * @hsink: (allow-none): a #GstStaticPadTemplate describing the harness sinkpad. + * %NULL will not create a harness sinkpad. + * @srcpad: (allow-none): a #gchar with the name of the element srcpad that is + * then linked to the harness sinkpad, similar to the @sinkpad. + * + * Creates a new harness. + * + * MT safe. + * + * Returns: (transfer full): a #GstHarness, or %NULL if the harness could + * not be created + * + * Since: 1.6 + */ +GstHarness * +gst_harness_new_full (GstElement * element, + GstStaticPadTemplate * hsrc, const gchar * sinkpad, + GstStaticPadTemplate * hsink, const gchar * srcpad) +{ + GstHarness *h; + GstHarnessPrivate *priv; + gboolean is_sink, is_src; + + g_return_val_if_fail (element != NULL, NULL); + + h = g_new0 (GstHarness, 1); + g_assert (h != NULL); + h->priv = g_new0 (GstHarnessPrivate, 1); + priv = h->priv; + + GST_DEBUG_OBJECT (h, "about to create new harness %p", h); + h->element = gst_object_ref (element); + priv->last_push_ts = GST_CLOCK_TIME_NONE; + priv->latency_min = 0; + priv->latency_max = GST_CLOCK_TIME_NONE; + priv->drop_buffers = FALSE; + + priv->propose_allocator = NULL; + gst_allocation_params_init (&priv->propose_allocation_params); + + g_mutex_init (&priv->blocking_push_mutex); + g_cond_init (&priv->blocking_push_cond); + + is_src = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SOURCE); + is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SINK); + + /* setup the loose srcpad linked to the element sinkpad */ + if (!is_src && hsrc) + gst_harness_setup_src_pad (h, hsrc, sinkpad); + + /* setup the loose sinkpad linked to the element srcpad */ + if (!is_sink && hsink) + gst_harness_setup_sink_pad (h, hsink, srcpad); + + /* as a harness sink, we should not need sync and async */ + if (is_sink) + turn_async_and_sync_off (h->element); + + if (h->srcpad != NULL) { + gchar *stream_id = g_strdup_printf ("%s-%p", + GST_OBJECT_NAME (h->element), h); + g_assert (gst_pad_push_event (h->srcpad, + gst_event_new_stream_start (stream_id))); + g_free (stream_id); + } + + /* don't start sources, they start producing data! */ + if (!is_src) + gst_harness_play (h); + + gst_harness_element_ref (h); + + GST_DEBUG_OBJECT (h, "created new harness %p " + "with srcpad (%p, %s, %s) and sinkpad (%p, %s, %s)", + h, h->srcpad, GST_DEBUG_PAD_NAME (h->srcpad), + h->sinkpad, GST_DEBUG_PAD_NAME (h->sinkpad)); + + priv->stress = g_ptr_array_new_with_free_func ( + (GDestroyNotify) gst_harness_stress_free); + + return h; +} + +/** + * gst_harness_new_with_element: (skip) + * @element: a #GstElement to attach the harness to (transfer none) + * @sinkpad: (allow-none): a #gchar with the name of the element sinkpad that + * is then linked to the harness srcpad. %NULL does not attach a sinkpad + * @srcpad: (allow-none): a #gchar with the name of the element srcpad that is + * then linked to the harness sinkpad. %NULL does not attach a srcpad + * + * Creates a new harness. Works in the same way as gst_harness_new_full, only + * that generic padtemplates are used for the harness src and sinkpads, which + * will be sufficient in most usecases. + * + * MT safe. + * + * Returns: (transfer full): a #GstHarness, or %NULL if the harness could + * not be created + * + * Since: 1.6 + */ +GstHarness * +gst_harness_new_with_element (GstElement * element, + const gchar * sinkpad, const gchar * srcpad) +{ + return gst_harness_new_full (element, + &hsrctemplate, sinkpad, &hsinktemplate, srcpad); +} + +/** + * gst_harness_new_with_padnames: (skip) + * @element_name: a #gchar describing the #GstElement name + * @sinkpad: (allow-none): a #gchar with the name of the element sinkpad that + * is then linked to the harness srcpad. %NULL does not attach a sinkpad + * @srcpad: (allow-none): a #gchar with the name of the element srcpad that is + * then linked to the harness sinkpad. %NULL does not attach a srcpad + * + * Creates a new harness. Works in the same way as gst_harness_new_with_element, + * except you specify the factoryname of the #GstElement + * + * MT safe. + * + * Returns: (transfer full): a #GstHarness, or %NULL if the harness could + * not be created + * + * Since: 1.6 + */ +GstHarness * +gst_harness_new_with_padnames (const gchar * element_name, + const gchar * sinkpad, const gchar * srcpad) +{ + GstHarness *h; + GstElement *element = gst_element_factory_make (element_name, NULL); + g_assert (element != NULL); + + h = gst_harness_new_with_element (element, sinkpad, srcpad); + gst_object_unref (element); + return h; +} + +/** + * gst_harness_new_with_templates: (skip) + * @element_name: a #gchar describing the #GstElement name + * @hsrc: (allow-none): a #GstStaticPadTemplate describing the harness srcpad. + * %NULL will not create a harness srcpad. + * @hsink: (allow-none): a #GstStaticPadTemplate describing the harness sinkpad. + * %NULL will not create a harness sinkpad. + * + * Creates a new harness, like gst_harness_new_full, except it + * assumes the #GstElement sinkpad is named "sink" and srcpad is named "src" + * + * MT safe. + * + * Returns: (transfer full): a #GstHarness, or %NULL if the harness could + * not be created + * + * Since: 1.6 + */ +GstHarness * +gst_harness_new_with_templates (const gchar * element_name, + GstStaticPadTemplate * hsrc, GstStaticPadTemplate * hsink) +{ + GstHarness *h; + GstElement *element = gst_element_factory_make (element_name, NULL); + g_assert (element != NULL); + + h = gst_harness_new_full (element, hsrc, "sink", hsink, "src"); + gst_object_unref (element); + return h; +} + +/** + * gst_harness_new: (skip) + * @element_name: a #gchar describing the #GstElement name + * + * Creates a new harness. Works like gst_harness_new_with_padnames, except it + * assumes the #GstElement sinkpad is named "sink" and srcpad is named "src" + * + * MT safe. + * + * Returns: (transfer full): a #GstHarness, or %NULL if the harness could + * not be created + * + * Since: 1.6 + */ +GstHarness * +gst_harness_new (const gchar * element_name) +{ + return gst_harness_new_with_padnames (element_name, "sink", "src"); +} + +/** + * gst_harness_new_parse: (skip) + * @launchline: a #gchar describing a gst-launch type line + * + * Creates a new harness, parsing the @launchline and putting that in a #GstBin, + * and then attches the harness to the bin. + * + * MT safe. + * + * Returns: (transfer full): a #GstHarness, or %NULL if the harness could + * not be created + * + * Since: 1.6 + */ +GstHarness * +gst_harness_new_parse (const gchar * launchline) +{ + GstHarness *h; + GstBin *bin; + gchar *desc; + GstPad *pad; + GstIterator *iter; + gboolean done = FALSE; + + g_return_val_if_fail (launchline != NULL, NULL); + + desc = g_strdup_printf ("bin.( %s )", launchline); + bin = + (GstBin *) gst_parse_launch_full (desc, NULL, GST_PARSE_FLAG_NONE, NULL); + g_free (desc); + + if (G_UNLIKELY (bin == NULL)) + return NULL; + + /* find pads and ghost them if necessary */ + if ((pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SRC)) != NULL) { + gst_element_add_pad (GST_ELEMENT (bin), gst_ghost_pad_new ("src", pad)); + gst_object_unref (pad); + } + if ((pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SINK)) != NULL) { + gst_element_add_pad (GST_ELEMENT (bin), gst_ghost_pad_new ("sink", pad)); + gst_object_unref (pad); + } + + iter = gst_bin_iterate_sinks (bin); + while (!done) { + GValue item = { 0, }; + + switch (gst_iterator_next (iter, &item)) { + case GST_ITERATOR_OK: + turn_async_and_sync_off (GST_ELEMENT (g_value_get_object (&item))); + g_value_reset (&item); + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + gst_object_unref (bin); + gst_iterator_free (iter); + g_return_val_if_reached (NULL); + break; + } + } + gst_iterator_free (iter); + + h = gst_harness_new_full (GST_ELEMENT_CAST (bin), + &hsrctemplate, "sink", &hsinktemplate, "src"); + gst_object_unref (bin); + return h; +} + +/** + * gst_harness_teardown: + * @h: a #GstHarness + * + * Tears down a @GstHarness, freeing all resources allocated using it. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_teardown (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + + if (priv->blocking_push_mode) { + g_mutex_lock (&priv->blocking_push_mutex); + priv->blocking_push_mode = FALSE; + g_cond_signal (&priv->blocking_push_cond); + g_mutex_unlock (&priv->blocking_push_mutex); + } + + if (h->src_harness) { + gst_harness_teardown (h->src_harness); + } + + if (h->sink_harness) { + gst_harness_teardown (h->sink_harness); + } + + if (priv->src_caps) + gst_caps_unref (priv->src_caps); + + if (priv->sink_caps) + gst_caps_unref (priv->sink_caps); + + if (h->srcpad) { + if (gst_pad_is_request_pad (GST_PAD_PEER (h->srcpad))) + gst_element_release_request_pad (h->element, GST_PAD_PEER (h->srcpad)); + g_free (priv->element_sinkpad_name); + + gst_pad_set_active (h->srcpad, FALSE); + gst_object_unref (h->srcpad); + + g_async_queue_unref (priv->src_event_queue); + } + + if (h->sinkpad) { + if (gst_pad_is_request_pad (GST_PAD_PEER (h->sinkpad))) + gst_element_release_request_pad (h->element, GST_PAD_PEER (h->sinkpad)); + g_free (priv->element_srcpad_name); + + gst_pad_set_active (h->sinkpad, FALSE); + gst_object_unref (h->sinkpad); + + g_async_queue_unref (priv->buffer_queue); + g_async_queue_unref (priv->sink_event_queue); + } + + if (priv->sink_forward_pad) + gst_object_unref (priv->sink_forward_pad); + + gst_object_replace ((GstObject **) & priv->propose_allocator, NULL); + gst_object_replace ((GstObject **) & priv->allocator, NULL); + gst_object_replace ((GstObject **) & priv->pool, NULL); + + /* if we hold the last ref, set to NULL */ + if (gst_harness_element_unref (h) == 0) { + GstState state, pending; + g_assert (gst_element_set_state (h->element, GST_STATE_NULL) == + GST_STATE_CHANGE_SUCCESS); + g_assert (gst_element_get_state (h->element, &state, &pending, 0) == + GST_STATE_CHANGE_SUCCESS); + g_assert (state == GST_STATE_NULL); + } + + g_cond_clear (&priv->blocking_push_cond); + g_mutex_clear (&priv->blocking_push_mutex); + + g_ptr_array_unref (priv->stress); + + gst_object_unref (h->element); + g_free (h->priv); + g_free (h); +} + +/** + * gst_harness_add_element_src_pad: + * @h: a #GstHarness + * @srcpad: a #GstPad to link to the harness sinkpad + * + * Links the specifed #GstPad the @GstHarness sinkpad. This can be useful if + * perhaps the srcpad did not exist at the time of creating the harness, + * like a demuxer that provides a sometimes-pad after receiving data. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_add_element_src_pad (GstHarness * h, GstPad * srcpad) +{ + GstHarnessPrivate *priv = h->priv; + gst_harness_setup_sink_pad (h, &hsinktemplate, NULL); + g_assert_cmpint (gst_pad_link (srcpad, h->sinkpad), ==, GST_PAD_LINK_OK); + g_free (priv->element_srcpad_name); + priv->element_srcpad_name = gst_pad_get_name (srcpad); +} + +/** + * gst_harness_add_element_sink_pad: + * @h: a #GstHarness + * @sinkpad: a #GstPad to link to the harness srcpad + * + * Links the specifed #GstPad the @GstHarness srcpad. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_add_element_sink_pad (GstHarness * h, GstPad * sinkpad) +{ + GstHarnessPrivate *priv = h->priv; + gst_harness_setup_src_pad (h, &hsrctemplate, NULL); + g_assert_cmpint (gst_pad_link (h->srcpad, sinkpad), ==, GST_PAD_LINK_OK); + g_free (priv->element_sinkpad_name); + priv->element_sinkpad_name = gst_pad_get_name (sinkpad); +} + +/** + * gst_harness_set_src_caps: + * @h: a #GstHarness + * @caps: (transfer full): a #GstCaps to set on the harness srcpad + * + * Sets the @GstHarness srcpad caps. This must be done before any buffers + * can legally be pushed from the harness to the element. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_src_caps (GstHarness * h, GstCaps * caps) +{ + GstHarnessPrivate *priv = h->priv; + GstSegment segment; + + g_assert (gst_pad_push_event (h->srcpad, gst_event_new_caps (caps))); + gst_caps_take (&priv->src_caps, caps); + + gst_segment_init (&segment, GST_FORMAT_TIME); + g_assert (gst_pad_push_event (h->srcpad, gst_event_new_segment (&segment))); +} + +/** + * gst_harness_set_sink_caps: + * @h: a #GstHarness + * @caps: (transfer full): a #GstCaps to set on the harness sinkpad + * + * Sets the @GstHarness sinkpad caps. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_sink_caps (GstHarness * h, GstCaps * caps) +{ + GstHarnessPrivate *priv = h->priv; + + gst_caps_take (&priv->sink_caps, caps); + gst_pad_push_event (h->sinkpad, gst_event_new_reconfigure ()); +} + +/** + * gst_harness_set_caps: + * @h: a #GstHarness + * @in: (transfer full): a #GstCaps to set on the harness srcpad + * @out: (transfer full): a #GstCaps to set on the harness sinkpad + * + * Sets the @GstHarness srcpad and sinkpad caps. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_caps (GstHarness * h, GstCaps * in, GstCaps * out) +{ + gst_harness_set_sink_caps (h, out); + gst_harness_set_src_caps (h, in); +} + +/** + * gst_harness_set_src_caps_str: + * @h: a #GstHarness + * @str: a @gchar describing a #GstCaps to set on the harness srcpad + * + * Sets the @GstHarness srcpad caps using a string. This must be done before + * any buffers can legally be pushed from the harness to the element. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_src_caps_str (GstHarness * h, const gchar * str) +{ + gst_harness_set_src_caps (h, gst_caps_from_string (str)); +} + +/** + * gst_harness_set_sink_caps_str: + * @h: a #GstHarness + * @str: a @gchar describing a #GstCaps to set on the harness sinkpad + * + * Sets the @GstHarness sinkpad caps using a string. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_sink_caps_str (GstHarness * h, const gchar * str) +{ + gst_harness_set_sink_caps (h, gst_caps_from_string (str)); +} + +/** + * gst_harness_set_caps_str: + * @h: a #GstHarness + * @in: a @gchar describing a #GstCaps to set on the harness srcpad + * @out: a @gchar describing a #GstCaps to set on the harness sinkpad + * + * Sets the @GstHarness srcpad and sinkpad caps using strings. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_caps_str (GstHarness * h, const gchar * in, const gchar * out) +{ + gst_harness_set_sink_caps_str (h, out); + gst_harness_set_src_caps_str (h, in); +} + +/** + * gst_harness_use_systemclock: + * @h: a #GstHarness + * + * Sets the system #GstClock on the @GstHarness #GstElement + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_use_systemclock (GstHarness * h) +{ + GstClock *clock = gst_system_clock_obtain (); + g_assert (clock != NULL); + gst_element_set_clock (h->element, clock); + gst_object_unref (clock); +} + +/** + * gst_harness_use_testclock: + * @h: a #GstHarness + * + * Sets the #GstTestClock on the #GstHarness #GstElement + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_use_testclock (GstHarness * h) +{ + GstClock *clock = gst_test_clock_new (); + g_assert (clock != NULL); + gst_element_set_clock (h->element, clock); + gst_object_unref (clock); +} + +/** + * gst_harness_get_testclock: + * @h: a #GstHarness + * + * Get the #GstTestClock. Useful if specific operations on the testclock is + * needed. + * + * MT safe. + * + * Returns: (transfer full): a #GstTestClock, or %NULL if the testclock is not + * present. + * + * Since: 1.6 + */ +GstTestClock * +gst_harness_get_testclock (GstHarness * h) +{ + GstTestClock *testclock = NULL; + GstClock *clock; + + clock = gst_element_get_clock (h->element); + if (clock) { + if (GST_IS_TEST_CLOCK (clock)) + testclock = GST_TEST_CLOCK (clock); + else + gst_object_unref (clock); + } + return testclock; +} + +/** + * gst_harness_set_time: + * @h: a #GstHarness + * @time: a #GstClockTime to advance the clock to + * + * Advance the #GstTestClock to a specific time. + * + * MT safe. + * + * Returns: a @gboolean %TRUE if the time could be set. %FALSE if not. + * + * Since: 1.6 + */ +gboolean +gst_harness_set_time (GstHarness * h, GstClockTime time) +{ + GstTestClock *testclock; + testclock = gst_harness_get_testclock (h); + if (testclock == NULL) + return FALSE; + + gst_test_clock_set_time (testclock, time); + gst_object_unref (testclock); + return TRUE; +} + +/** + * gst_harness_wait_for_clock_id_waits: + * @h: a #GstHarness + * @waits: a #guint describing the numbers of #GstClockID registered with + * the #GstTestClock + * @timeout: a #guint describing how many seconds to wait for @waits to be true + * + * Waits for @timeout seconds until @waits number of #GstClockID waits is + * registered with the #GstTestClock. Useful for writing deterministic tests, + * where you want to make sure that an expected number of waits have been + * reached. + * + * MT safe. + * + * Returns: a @gboolean %TRUE if the waits have been registered, %FALSE if not. + * (Could be that it timed out waiting or that more waits then waits was found) + * + * Since: 1.6 + */ +gboolean +gst_harness_wait_for_clock_id_waits (GstHarness * h, guint waits, guint timeout) +{ + GstTestClock *testclock = gst_harness_get_testclock (h); + gint64 start_time; + gboolean ret; + + if (testclock == NULL) + return FALSE; + + start_time = g_get_monotonic_time (); + while (gst_test_clock_peek_id_count (testclock) < waits) { + gint64 time_spent; + + g_usleep (G_USEC_PER_SEC / 1000); + time_spent = g_get_monotonic_time () - start_time; + if ((time_spent / G_USEC_PER_SEC) > timeout) + break; + } + + ret = (waits == gst_test_clock_peek_id_count (testclock)); + + gst_object_unref (testclock); + return ret; +} + +/** + * gst_harness_crank_single_clock_wait: + * @h: a #GstHarness + * + * A "crank" consists of three steps: + * 1: Wait for a #GstClockID to be registered with the #GstTestClock. + * 2: Advance the #GstTestClock to the time the #GstClockID is waiting for. + * 3: Release the #GstClockID wait. + * Together, this provides an easy way to not have to think about the details + * around clocks and time, but still being able to write deterministic tests + * that are dependant on this. A "crank" can be though of as the notion of + * manually driving the clock forward to its next logical step. + * + * MT safe. + * + * Returns: a @gboolean %TRUE if the "crank" was successful, %FALSE if not. + * + * Since: 1.6 + */ +gboolean +gst_harness_crank_single_clock_wait (GstHarness * h) +{ + GstTestClock *testclock = gst_harness_get_testclock (h); + GstClockID res, pending; + gboolean ret = FALSE; + + if (G_LIKELY (testclock != NULL)) { + gst_test_clock_wait_for_next_pending_id (testclock, &pending); + + gst_test_clock_set_time (testclock, gst_clock_id_get_time (pending)); + res = gst_test_clock_process_next_clock_id (testclock); + if (res == pending) { + GST_DEBUG ("cranked time %" GST_TIME_FORMAT, + GST_TIME_ARGS (gst_clock_get_time (GST_CLOCK (testclock)))); + ret = TRUE; + } else { + GST_WARNING ("testclock next id != pending (%p != %p)", res, pending); + } + + gst_clock_id_unref (res); + gst_clock_id_unref (pending); + + gst_object_unref (testclock); + } else { + GST_WARNING ("No testclock on element %s", GST_ELEMENT_NAME (h->element)); + } + + return ret; +} + +/** + * gst_harness_crank_multiple_clock_waits: + * @h: a #GstHarness + * @waits: a #guint describing the number of #GstClockIDs to crank + * + * Similar to gst_harness_crank_single_clock_wait, this is the function to use + * if your harnessed element(s) are using more then one gst_clock_id_wait. + * Failing to do so can (and will) make it racy which #GstClockID you actually + * are releasing, where as this function will process all the waits at the + * same time, ensuring that one thread can't register another wait before + * both are released. + * + * MT safe. + * + * Returns: a @gboolean %TRUE if the "crank" was successful, %FALSE if not. + * + * Since: 1.6 + */ +gboolean +gst_harness_crank_multiple_clock_waits (GstHarness * h, guint waits) +{ + GstTestClock *testclock; + GList *pending; + guint processed; + + testclock = gst_harness_get_testclock (h); + if (testclock == NULL) + return FALSE; + + gst_test_clock_wait_for_multiple_pending_ids (testclock, waits, &pending); + gst_harness_set_time (h, gst_test_clock_id_list_get_latest_time (pending)); + processed = gst_test_clock_process_id_list (testclock, pending); + + g_list_free_full (pending, gst_clock_id_unref); + gst_object_unref (testclock); + return processed == waits; +} + +/** + * gst_harness_play: + * @h: a #GstHarness + * + * This will set the harnessed #GstElement to %GST_STATE_PLAYING. + * #GstElements without a sink-#GstPad and with the %GST_ELEMENT_FLAG_SOURCE + * flag set is concidered a src #GstElement + * Non-src #GstElements (like sinks and filters) are automatically set to + * playing by the #GstHarness, but src #GstElements are not to avoid them + * starting to produce buffers. + * Hence, for src #GstElement you will need to call gst_harness_play explicitly. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_play (GstHarness * h) +{ + GstState state, pending; + g_assert_cmpint (GST_STATE_CHANGE_SUCCESS, ==, + gst_element_set_state (h->element, GST_STATE_PLAYING)); + g_assert_cmpint (GST_STATE_CHANGE_SUCCESS, ==, + gst_element_get_state (h->element, &state, &pending, 0)); + g_assert_cmpint (GST_STATE_PLAYING, ==, state); +} + +/** + * gst_harness_set_blocking_push_mode: + * @h: a #GstHarness + * + * Setting this will make the harness block in the chain-function, and + * then release when gst_harness_pull or gst_harness_try_pull is called. + * Can be useful when wanting to control a src-element that is not implementing + * gst_clock_id_wait so it can't be controlled by the #GstTestClock, since + * it otherwise would produce buffers as fast as possible. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_blocking_push_mode (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + priv->blocking_push_mode = TRUE; +} + +/** + * gst_harness_create_buffer: + * @h: a #GstHarness + * @size: a #gsize specifying the size of the buffer + * + * Allocates a buffer using a #GstBufferPool if present, or else using the + * configured #GstAllocator and #GstAllocatorParams + * + * MT safe. + * + * Returns: a #GstBuffer of size @size + * + * Since: 1.6 + */ +GstBuffer * +gst_harness_create_buffer (GstHarness * h, gsize size) +{ + GstHarnessPrivate *priv = h->priv; + GstBuffer *ret = NULL; + + if (gst_pad_check_reconfigure (h->srcpad)) + gst_harness_negotiate (h); + + if (priv->pool) { + g_assert_cmpint (gst_buffer_pool_acquire_buffer (priv->pool, &ret, NULL), ==, + GST_FLOW_OK); + if (gst_buffer_get_size (ret) != size) { + GST_DEBUG_OBJECT (h, + "use fallback, pool is configured with a different size (%zu != %zu)", + size, gst_buffer_get_size (ret)); + gst_buffer_unref (ret); + ret = NULL; + } + } + + if (!ret) + ret = gst_buffer_new_allocate (priv->allocator, size, &priv->allocation_params); + + g_assert (ret != NULL); + return ret; +} + +/** + * gst_harness_push: + * @h: a #GstHarness + * @buffer: a #GstBuffer to push + * + * Pushes a #GstBuffer on the #GstHarness srcpad. The standard way of + * interacting with an harnessed element. + * + * MT safe. + * + * Returns: a #GstFlowReturn with the result from the push + * + * Since: 1.6 + */ +GstFlowReturn +gst_harness_push (GstHarness * h, GstBuffer * buffer) +{ + GstHarnessPrivate *priv = h->priv; + g_assert (buffer != NULL); + priv->last_push_ts = GST_BUFFER_TIMESTAMP (buffer); + return gst_pad_push (h->srcpad, buffer); +} + +/** + * gst_harness_pull: + * @h: a #GstHarness + * + * Pulls a #GstBuffer from the #GAsyncQueue on the #GstHarness sinkpad. The pull + * will timeout in 60 seconds. This is the standard way of getting a buffer + * from a harnessed #GstElement. + * + * MT safe. + * + * Returns: a #GstBuffer or %NULL if timed out. + * + * Since: 1.6 + */ +GstBuffer * +gst_harness_pull (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + + if (priv->blocking_push_mode) { + g_mutex_lock (&priv->blocking_push_mutex); + g_cond_signal (&priv->blocking_push_cond); + g_mutex_unlock (&priv->blocking_push_mutex); + } + + return (GstBuffer *) g_async_queue_timeout_pop (priv->buffer_queue, + G_USEC_PER_SEC * 60); +} + +/** + * gst_harness_try_pull: + * @h: a #GstHarness + * + * Pulls a #GstBuffer from the #GAsyncQueue on the #GstHarness sinkpad. Unlike + * gst_harness_pull this will not wait for any buffers if not any are present, + * and return %NULL straight away. + * + * MT safe. + * + * Returns: a #GstBuffer or %NULL if no buffers are present in the #GAsyncQueue + * + * Since: 1.6 + */ +GstBuffer * +gst_harness_try_pull (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + + if (priv->blocking_push_mode) { + g_mutex_lock (&priv->blocking_push_mutex); + g_cond_signal (&priv->blocking_push_cond); + g_mutex_unlock (&priv->blocking_push_mutex); + } + + return (GstBuffer *) g_async_queue_try_pop (priv->buffer_queue); +} + +/** + * gst_harness_push_and_pull: + * @h: a #GstHarness + * @buffer: a #GstBuffer to push + * + * Basically a gst_harness_push and a gst_harness_pull in one line. Reflects + * the fact that you often want to do exactly this in your test: Push one buffer + * in, and inspect the outcome. + * + * MT safe. + * + * Returns: a #GstBuffer or %NULL if timed out. + * + * Since: 1.6 + */ +GstBuffer * +gst_harness_push_and_pull (GstHarness * h, GstBuffer * buffer) +{ + gst_harness_push (h, buffer); + return gst_harness_pull (h); +} + +/** + * gst_harness_buffers_received: + * @h: a #GstHarness + * + * The total number of #GstBuffers that has arrived on the #GstHarness sinkpad. + * This number includes buffers that have been dropped as well as buffers + * that have already been pulled out. + * + * MT safe. + * + * Returns: a #guint number of buffers received + * + * Since: 1.6 + */ +guint +gst_harness_buffers_received (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return g_atomic_int_get (&priv->recv_buffers); +} + +/** + * gst_harness_buffers_in_queue: + * @h: a #GstHarness + * + * The number of #GstBuffers currently in the #GstHarness sinkpad #GAsyncQueue + * + * MT safe. + * + * Returns: a #guint number of buffers in the queue + * + * Since: 1.6 + */ +guint +gst_harness_buffers_in_queue (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return g_async_queue_length (priv->buffer_queue); +} + +/** + * gst_harness_set_drop_buffers: + * @h: a #GstHarness + * @drop_buffers: a #gboolean specifying to drop outgoing buffers or not + * + * When set to %TRUE, instead of placing the buffers arriving from the harnessed + * #GstElement inside the sinkpads #GAsyncQueue, they are instead unreffed. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_drop_buffers (GstHarness * h, gboolean drop_buffers) +{ + GstHarnessPrivate *priv = h->priv; + priv->drop_buffers = drop_buffers; +} + +/** + * gst_harness_dump_to_file: + * @h: a #GstHarness + * @filename: a #gchar with a the name of a file + * + * Allows you to dump the #GstBuffers the #GstHarness sinkpad #GAsyncQueue + * to a file. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_dump_to_file (GstHarness * h, const gchar * filename) +{ + GstHarnessPrivate *priv = h->priv; + FILE *fd; + GstBuffer *buf; + fd = fopen (filename, "wb"); + g_assert (fd); + + while ((buf = g_async_queue_try_pop (priv->buffer_queue))) { + GstMapInfo info; + gst_buffer_map (buf, &info, GST_MAP_READ); + fwrite (info.data, 1, info.size, fd); + gst_buffer_unmap (buf, &info); + gst_buffer_unref (buf); + } + + fflush (fd); + fclose (fd); +} + +/** + * gst_harness_get_last_pushed_timestamp: + * @h: a #GstHarness + * + * Get the timestamp of the last #GstBuffer pushed on the #GstHarness srcpad, + * typically with gst_harness_push or gst_harness_push_from_src. + * + * MT safe. + * + * Returns: a #GstClockTime with the timestamp or %GST_CLOCK_TIME_NONE if no + * #GstBuffer has been pushed on the #GstHarness srcpad + * + * Since: 1.6 + */ +GstClockTime +gst_harness_get_last_pushed_timestamp (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return priv->last_push_ts; +} + +/** + * gst_harness_push_event: + * @h: a #GstHarness + * @event: a #GstEvent to push + * + * Pushes an #GstEvent on the #GstHarness srcpad. + * + * MT safe. + * + * Returns: a #gboolean with the result from the push + * + * Since: 1.6 + */ +gboolean +gst_harness_push_event (GstHarness * h, GstEvent * event) +{ + return gst_pad_push_event (h->srcpad, event); +} + +/** + * gst_harness_pull_event: + * @h: a #GstHarness + * + * Pulls an #GstEvent from the #GAsyncQueue on the #GstHarness sinkpad. + * Timeouts after 60 seconds similar to gst_harness_pull. + * + * MT safe. + * + * Returns: a #GstEvent or %NULL if timed out. + * + * Since: 1.6 + */ +GstEvent * +gst_harness_pull_event (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return (GstEvent *) g_async_queue_timeout_pop (priv->sink_event_queue, + G_USEC_PER_SEC * 60); +} + +/** + * gst_harness_try_pull_event: + * @h: a #GstHarness + * + * Pulls an #GstEvent from the #GAsyncQueue on the #GstHarness sinkpad. + * See gst_harness_try_pull for details. + * + * MT safe. + * + * Returns: a #GstEvent or %NULL if no buffers are present in the #GAsyncQueue + * + * Since: 1.6 + */ +GstEvent * +gst_harness_try_pull_event (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return (GstEvent *) g_async_queue_try_pop (priv->sink_event_queue); +} + +/** + * gst_harness_events_received: + * @h: a #GstHarness + * + * The total number of #GstEvents that has arrived on the #GstHarness sinkpad + * This number includes events handled by the harness as well as events + * that have already been pulled out. + * + * MT safe. + * + * Returns: a #guint number of events received + * + * Since: 1.6 + */ +guint +gst_harness_events_received (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return g_atomic_int_get (&priv->recv_events); +} + +/** + * gst_harness_events_in_queue: + * @h: a #GstHarness + * + * The number of #GstEvents currently in the #GstHarness sinkpad #GAsyncQueue + * + * MT safe. + * + * Returns: a #guint number of events in the queue + * + * Since: 1.6 + */ +guint +gst_harness_events_in_queue (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return g_async_queue_length (priv->sink_event_queue); +} + +/** + * gst_harness_push_upstream_event: + * @h: a #GstHarness + * @event: a #GstEvent to push + * + * Pushes an #GstEvent on the #GstHarness sinkpad. + * + * MT safe. + * + * Returns: a #gboolean with the result from the push + * + * Since: 1.6 + */ +gboolean +gst_harness_push_upstream_event (GstHarness * h, GstEvent * event) +{ + g_return_val_if_fail (event != NULL, FALSE); + g_return_val_if_fail (GST_EVENT_IS_UPSTREAM (event), FALSE); + + return gst_pad_push_event (h->sinkpad, event); +} + +/** + * gst_harness_pull_upstream_event: + * @h: a #GstHarness + * + * Pulls an #GstEvent from the #GAsyncQueue on the #GstHarness srcpad. + * Timeouts after 60 seconds similar to gst_harness_pull. + * + * MT safe. + * + * Returns: a #GstEvent or %NULL if timed out. + * + * Since: 1.6 + */ +GstEvent * +gst_harness_pull_upstream_event (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return (GstEvent *) g_async_queue_timeout_pop (priv->src_event_queue, + G_USEC_PER_SEC * 60); +} + +/** + * gst_harness_try_pull_upstream_event: + * @h: a #GstHarness + * + * Pulls an #GstEvent from the #GAsyncQueue on the #GstHarness srcpad. + * See gst_harness_try_pull for details. + * + * MT safe. + * + * Returns: a #GstEvent or %NULL if no buffers are present in the #GAsyncQueue + * + * Since: 1.6 + */ +GstEvent * +gst_harness_try_pull_upstream_event (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return (GstEvent *) g_async_queue_try_pop (priv->src_event_queue); +} + +/** + * gst_harness_upstream_events_received: + * @h: a #GstHarness + * + * The total number of #GstEvents that has arrived on the #GstHarness srcpad + * This number includes events handled by the harness as well as events + * that have already been pulled out. + * + * MT safe. + * + * Returns: a #guint number of events received + * + * Since: 1.6 + */ +guint +gst_harness_upstream_events_received (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return g_atomic_int_get (&priv->recv_upstream_events); +} + +/** + * gst_harness_upstream_events_in_queue: + * @h: a #GstHarness + * + * The number of #GstEvents currently in the #GstHarness srcpad #GAsyncQueue + * + * MT safe. + * + * Returns: a #guint number of events in the queue + * + * Since: 1.6 + */ +guint +gst_harness_upstream_events_in_queue (GstHarness * h) +{ + GstHarnessPrivate *priv = h->priv; + return g_async_queue_length (priv->src_event_queue); +} + +/** + * gst_harness_query_latency: + * @h: a #GstHarness + * + * Get the min latency reported by any harnessed #GstElement. + * + * MT safe. + * + * Returns: a #GstClockTime with min latency + * + * Since: 1.6 + */ +GstClockTime +gst_harness_query_latency (GstHarness * h) +{ + GstQuery *query; + gboolean is_live; + GstClockTime min = GST_CLOCK_TIME_NONE; + GstClockTime max; + + query = gst_query_new_latency (); + + if (gst_pad_peer_query (h->sinkpad, query)) { + gst_query_parse_latency (query, &is_live, &min, &max); + } + gst_query_unref (query); + + return min; +} + +/** + * gst_harness_set_upstream_latency: + * @h: a #GstHarness + * @latency: a #GstClockTime specifying the latency + * + * Sets the min latency reported by #GstHarness when receiving a latency-query + * + * MT safe. + * + * Returns: a #GstClockTime with min latency + * + * Since: 1.6 + */ +void +gst_harness_set_upstream_latency (GstHarness * h, GstClockTime latency) +{ + GstHarnessPrivate *priv = h->priv; + priv->latency_min = latency; +} + +/** + * gst_harness_get_allocator: + * @h: a #GstHarness + * @allocator: (out) (allow-none) (transfer none): the #GstAllocator + * used + * @params: (out) (allow-none) (transfer full): the + * #GstAllocatorParams of @allocator + * + * Gets the @allocator and its @params that has been decided to use after an + * allocation query. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_get_allocator (GstHarness * h, GstAllocator ** allocator, + GstAllocationParams * params) +{ + GstHarnessPrivate *priv = h->priv; + if (allocator) + *allocator = priv->allocator; + if (params) + *params = priv->allocation_params; +} + + +/** + * gst_harness_get_allocator: + * @h: a #GstHarness + * @allocator: (allow-none) (transfer full): a #GstAllocator + * @params: (allow-none) (transfer none): a #GstAllocationParams + * + * Sets the @allocator and @params to propose when receiving an allocation + * query. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set_propose_allocator (GstHarness * h, GstAllocator * allocator, + const GstAllocationParams * params) +{ + GstHarnessPrivate *priv = h->priv; + if (allocator) + priv->propose_allocator = allocator; + if (params) + priv->propose_allocation_params = *params; +} + +static void +gst_harness_setup_src_harness (GstHarness * h, gboolean has_clock_wait) +{ + h->src_harness->priv->sink_forward_pad = gst_object_ref (h->srcpad); + gst_harness_use_testclock (h->src_harness); + h->src_harness->priv->has_clock_wait = has_clock_wait; +} + +/** + * gst_harness_add_src: + * @h: a #GstHarness + * @src_element_name: a #gchar with the name of a #GstElement + * @has_clock_wait: a #gboolean specifying if the #GstElement uses + * gst_clock_wait_id internally. + * + * A src-harness is a great way of providing the #GstHarness with data. + * By adding a src-type #GstElement, it is then easy to use functions like + * gst_harness_push_from_src or gst_harness_src_crank_and_push_many + * to provide your harnessed element with input. The @has_clock_wait variable + * is a greate way to control you src-element with, in that you can have it + * produce a buffer for you by simply cranking the clock, and not have it + * spin out of control producing buffers as fast as possible. + * + * If a src-harness already exists it will be replaced. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_add_src (GstHarness * h, + const gchar * src_element_name, gboolean has_clock_wait) +{ + if (h->src_harness) + gst_harness_teardown (h->src_harness); + h->src_harness = gst_harness_new (src_element_name); + gst_harness_setup_src_harness (h, has_clock_wait); +} + +/** + * gst_harness_add_src_parse: + * @h: a #GstHarness + * @launchline: a #gchar describing a gst-launch type line + * @has_clock_wait: a #gboolean specifying if the #GstElement uses + * gst_clock_wait_id internally. + * + * Similar to gst_harness_add_src, this allows you to specify a launch-line, + * which can be useful for both having more then one #GstElement acting as your + * src (Like a src producing raw buffers, and then an encoder, providing encoded + * data), but also by allowing you to set properties like "is-live" directly on + * the elements. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_add_src_parse (GstHarness * h, + const gchar * launchline, gboolean has_clock_wait) +{ + if (h->src_harness) + gst_harness_teardown (h->src_harness); + h->src_harness = gst_harness_new_parse (launchline); + gst_harness_setup_src_harness (h, has_clock_wait); +} + +/** + * gst_harness_push_from_src: + * @h: a #GstHarness + * + * Transfer data from the src-#GstHarness to the main-#GstHarness. It consists + * of 4 steps: + * 1: Make sure the src is started. (see: gst_harness_play) + * 2: Crank the clock (see: gst_harness_crank_single_clock_wait) + * 3: Pull a #GstBuffer from the src-#GstHarness (see: gst_harness_pull) + * 4: Push the same #GstBuffer into the main-#GstHarness (see: gst_harness_push) + * + * MT safe. + * + * Returns: a #GstFlowReturn with the result of the push + * + * Since: 1.6 + */ +GstFlowReturn +gst_harness_push_from_src (GstHarness * h) +{ + GstBuffer *buf; + + g_assert (h->src_harness); + + /* FIXME: this *is* the right time to start the src, + but maybe a flag so we don't keep telling it to play? */ + gst_harness_play (h->src_harness); + + if (h->src_harness->priv->has_clock_wait) { + g_assert (gst_harness_crank_single_clock_wait (h->src_harness)); + } + + g_assert ((buf = gst_harness_pull (h->src_harness)) != NULL); + return gst_harness_push (h, buf); +} + +/** + * gst_harness_src_crank_and_push_many: + * @h: a #GstHarness + * @cranks: a #gint with the number of calls to gst_harness_crank_single_clock_wait + * @pushes: a #gint with the number of calls to gst_harness_push + * + * Transfer data from the src-#GstHarness to the main-#GstHarness. Similar to + * gst_harness_push_from_src, this variant allows you to specify how many cranks + * and how many pushes to perform. This can be useful for both moving a lot + * of data at the same time, as well as cases when one crank does not equal one + * buffer to push and v.v. + * + * MT safe. + * + * Returns: a #GstFlowReturn with the result of the push + * + * Since: 1.6 + */ +GstFlowReturn +gst_harness_src_crank_and_push_many (GstHarness * h, gint cranks, gint pushes) +{ + GstFlowReturn ret = GST_FLOW_OK; + + g_assert (h->src_harness); + gst_harness_play (h->src_harness); + + for (int i = 0; i < cranks; i++) + g_assert (gst_harness_crank_single_clock_wait (h->src_harness)); + + for (int i = 0; i < pushes; i++) { + GstBuffer *buf; + g_assert ((buf = gst_harness_pull (h->src_harness)) != NULL); + ret = gst_harness_push (h, buf); + if (ret != GST_FLOW_OK) + break; + } + + return ret; +} + +/** + * gst_harness_src_push_event: + * @h: a #GstHarness + * + * Similar to what gst_harness_src_push does with #GstBuffers, this transfers + * a #GstEvent from the src-#GstHarness to the main-#GstHarness. Note that + * some #GstEvents are being transferred automagically. Look at sink_forward_pad + * for details. + * + * MT safe. + * + * Returns: a #gboolean with the result of the push + * + * Since: 1.6 + */ +gboolean +gst_harness_src_push_event (GstHarness * h) +{ + return gst_harness_push_event (h, gst_harness_pull_event (h->src_harness)); +} + +/** + * gst_harness_add_sink: + * @h: a #GstHarness + * @sink_element_name: a #gchar with the name of a #GstElement + * + * Similar to gst_harness_add_src, this allows you to send the data coming out + * of your harnessed #GstElement to a sink-element, allowing to test different + * responses the element output might create in sink elements. An example might + * be an existing sink providing some analytical data on the input it receives that + * can be useful to your testing. If the goal is to test a sink-element itself, + * this is better acheived using gst_harness_new directly on the sink. + * + * If a sink-harness already exists it will be replaced. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_add_sink (GstHarness * h, const gchar * sink_element_name) +{ + GstHarnessPrivate *priv = h->priv; + + if (h->sink_harness) { + gst_harness_teardown (h->sink_harness); + gst_object_unref (priv->sink_forward_pad); + } + h->sink_harness = gst_harness_new (sink_element_name); + priv->sink_forward_pad = gst_object_ref (h->sink_harness->srcpad); +} + +/** + * gst_harness_add_sink_parse: + * @h: a #GstHarness + * @launchline: a #gchar with the name of a #GstElement + * + * Similar to gst_harness_add_sink, this allows you to specify a launch-line + * instead of just an element name. See gst_harness_add_src_parse for details. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_add_sink_parse (GstHarness * h, const gchar * launchline) +{ + GstHarnessPrivate *priv = h->priv; + + if (h->sink_harness) { + gst_harness_teardown (h->sink_harness); + gst_object_unref (priv->sink_forward_pad); + } + h->sink_harness = gst_harness_new_parse (launchline); + priv->sink_forward_pad = gst_object_ref (h->sink_harness->srcpad); +} + +/** + * gst_harness_push_to_sink: + * @h: a #GstHarness + * + * Transfer one #GstBuffer from the main-#GstHarness to the sink-#GstHarness. + * See gst_harness_push_from_src for details. + * + * MT safe. + * + * Returns: a #GstFlowReturn with the result of the push + * + * Since: 1.6 + */ +GstFlowReturn +gst_harness_push_to_sink (GstHarness * h) +{ + GstBuffer *buf; + g_assert (h->sink_harness); + g_assert ((buf = gst_harness_pull (h)) != NULL); + return gst_harness_push (h->sink_harness, buf); +} + +/** + * gst_harness_sink_push_many: + * @h: a #GstHarness + * @pushes: a #gint with the number of calls to gst_harness_push_to_sink + * + * Convenience that calls gst_harness_push_to_sink @pushes number of times. + * Will abort the pushing if any one push fails. + * + * MT safe. + * + * Returns: a #GstFlowReturn with the result of the push + * + * Since: 1.6 + */ +GstFlowReturn +gst_harness_sink_push_many (GstHarness * h, gint pushes) +{ + GstFlowReturn ret = GST_FLOW_OK; + g_assert (h->sink_harness); + for (int i = 0; i < pushes; i++) { + ret = gst_harness_push_to_sink (h); + if (ret != GST_FLOW_OK) + break; + } + return ret; +} + +/** + * gst_harness_find_element: + * @h: a #GstHarness + * @element_name: a #gchar with a #GstElementFactory name + * + * Most useful in conjunction with gst_harness_new_parse, this will scan the + * #GstElements inside the #GstHarness, and check if any of them matches + * @element_name. Typical usecase being that you need to access one of the + * harnessed elements for properties and/or signals. + * + * MT safe. + * + * Returns: (transfer full) (allow-none): a #GstElement or %NULL if not found + * + * Since: 1.6 + */ +GstElement * +gst_harness_find_element (GstHarness * h, const gchar * element_name) +{ + gboolean done = FALSE; + GstIterator *iter; + GValue data = G_VALUE_INIT; + + iter = gst_bin_iterate_elements (GST_BIN (h->element)); + done = FALSE; + + while (!done) { + switch (gst_iterator_next (iter, &data)) { + case GST_ITERATOR_OK: + { + GstElement *element = g_value_get_object (&data); + GstPluginFeature *feature = + GST_PLUGIN_FEATURE (gst_element_get_factory (element)); + if (!strcmp (element_name, gst_plugin_feature_get_name (feature))) { + gst_iterator_free (iter); + return element; + } + g_value_reset (&data); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + + return NULL; +} + +/** + * gst_harness_set: + * @h: a #GstHarness + * @element_name: a #gchar with a #GstElementFactory name + * @first_property_name: a #gchar with the first property name + * + * A convenience function to allows you to call g_object_set on a #GstElement + * that are residing inside the #GstHarness, by using normal g_object_set + * syntax. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_set (GstHarness * h, + const gchar * element_name, const gchar * first_property_name, ...) +{ + va_list var_args; + GstElement *element = gst_harness_find_element (h, element_name); + va_start (var_args, first_property_name); + g_object_set_valist (G_OBJECT (element), first_property_name, var_args); + va_end (var_args); + gst_object_unref (element); +} + +/** + * gst_harness_get: + * @h: a #GstHarness + * @element_name: a #gchar with a #GstElementFactory name + * @first_property_name: a #gchar with the first property name + * + * A convenience function to allows you to call g_object_get on a #GstElement + * that are residing inside the #GstHarness, by using normal g_object_get + * syntax. + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_get (GstHarness * h, + const gchar * element_name, const gchar * first_property_name, ...) +{ + va_list var_args; + GstElement *element = gst_harness_find_element (h, element_name); + va_start (var_args, first_property_name); + g_object_get_valist (G_OBJECT (element), first_property_name, var_args); + va_end (var_args); + gst_object_unref (element); +} + +/** + * gst_harness_add_probe: + * @h: a #GstHarness + * @element_name: a #gchar with a #GstElementFactory name + * @pad_name: a #gchar with the name of the pad to attach the probe to + * @mask: a #GstPadProbeType (see gst_pad_add_probe) + * @callback: a #GstPadProbeCallback (see gst_pad_add_probe) + * @user_data: a #gpointer (see gst_pad_add_probe) + * @destroy_data: a #GDestroyNotify (see gst_pad_add_probe) + * + * A convenience function to allows you to call gst_pad_add_probe on a + * #GstPad of a #GstElement that are residing inside the #GstHarness, + * by using normal gst_pad_add_probe syntax + * + * MT safe. + * + * Since: 1.6 + */ +void +gst_harness_add_probe (GstHarness * h, + const gchar * element_name, const gchar * pad_name, GstPadProbeType mask, + GstPadProbeCallback callback, gpointer user_data, + GDestroyNotify destroy_data) +{ + GstElement *element = gst_harness_find_element (h, element_name); + GstPad *pad = gst_element_get_static_pad (element, pad_name); + gst_pad_add_probe (pad, mask, callback, user_data, destroy_data); + gst_object_unref (pad); + gst_object_unref (element); +} + +/******************************************************************************/ +/* STRESS */ +/******************************************************************************/ +struct _GstHarnessThread +{ + GstHarness *h; + GThread *thread; + gboolean running; + + gulong sleep; + + GDestroyNotify freefunc; +}; + +typedef struct +{ + GstHarnessThread t; + + GFunc init; + GFunc callback; + gpointer data; +} GstHarnessCustomThread; + +typedef struct +{ + GstHarnessThread t; + + GstCaps *caps; + GstSegment segment; + GstHarnessPrepareBuffer func; + gpointer data; + GDestroyNotify notify; +} GstHarnessPushBufferThread; + +typedef struct +{ + GstHarnessThread t; + + GstEvent *event; +} GstHarnessPushEventThread; + +typedef struct +{ + GstHarnessThread t; + + gchar *name; + GValue value; +} GstHarnessPropThread; + +typedef struct +{ + GstHarnessThread t; + + GstPadTemplate *templ; + gchar *name; + GstCaps *caps; + gboolean release; + + GSList *pads; +} GstHarnessReqPadThread; + +static void +gst_harness_thread_init (GstHarnessThread * t, GDestroyNotify freefunc, + GstHarness * h, gulong sleep) +{ + t->freefunc = freefunc; + t->h = h; + t->sleep = sleep; + + g_ptr_array_add (h->priv->stress, t); +} + +static void +gst_harness_thread_free (GstHarnessThread * t) +{ + g_slice_free (GstHarnessThread, t); +} + +static void +gst_harness_custom_thread_free (GstHarnessCustomThread * t) +{ + g_slice_free (GstHarnessCustomThread, t); +} + +static void +gst_harness_push_buffer_thread_free (GstHarnessPushBufferThread * t) +{ + if (t != NULL) { + gst_caps_replace (&t->caps, NULL); + if (t->notify != NULL) + t->notify (t->data); + g_slice_free (GstHarnessPushBufferThread, t); + } +} + +static void +gst_harness_push_event_thread_free (GstHarnessPushEventThread * t) +{ + if (t != NULL) { + gst_event_replace (&t->event, NULL); + g_slice_free (GstHarnessPushEventThread, t); + } +} + +static void +gst_harness_property_thread_free (GstHarnessPropThread * t) +{ + if (t != NULL) { + g_free (t->name); + g_value_unset (&t->value); + g_slice_free (GstHarnessPropThread, t); + } +} + +static void +gst_harness_requestpad_release (GstPad * pad, GstElement * element) +{ + gst_element_release_request_pad (element, pad); + gst_object_unref (pad); +} + +static void +gst_harness_requestpad_release_pads (GstHarnessReqPadThread * rpt) +{ + g_slist_foreach (rpt->pads, (GFunc) gst_harness_requestpad_release, + rpt->t.h->element); + g_slist_free (rpt->pads); + rpt->pads = NULL; +} + +static void +gst_harness_requestpad_thread_free (GstHarnessReqPadThread * t) +{ + if (t != NULL) { + gst_object_replace ((GstObject **) & t->templ, NULL); + g_free (t->name); + gst_caps_replace (&t->caps, NULL); + + gst_harness_requestpad_release_pads (t); + g_slice_free (GstHarnessReqPadThread, t); + } +} + +#define GST_HARNESS_THREAD_START(ID, t) \ + (((GstHarnessThread *)t)->running = TRUE, \ + ((GstHarnessThread *)t)->thread = g_thread_new ( \ + "gst-harness-stress-"G_STRINGIFY(ID), \ + (GThreadFunc)gst_harness_stress_##ID##_func, t)) +#define GST_HARNESS_THREAD_END(t) \ + (t->running = FALSE, \ + GPOINTER_TO_UINT (g_thread_join (t->thread))) + +#define GST_HARNESS_STRESS_FUNC_BEGIN(ID, INIT) \ + static gpointer \ + gst_harness_stress_##ID##_func (GstHarnessThread * t) \ + { \ + guint count = 0; \ + INIT; \ + \ + while (t->running) { + +#define GST_HARNESS_STRESS_FUNC_END() \ + count++; \ + g_usleep (t->sleep); \ + } \ + return GUINT_TO_POINTER (count); \ + } + +static void +gst_harness_stress_free (GstHarnessThread * t) +{ + if (t != NULL && t->freefunc != NULL) + t->freefunc (t); +} + +GST_HARNESS_STRESS_FUNC_BEGIN (custom, { + GstHarnessCustomThread *ct = (GstHarnessCustomThread *) t; + ct->init (ct, ct->data); + } +) +{ + GstHarnessCustomThread *ct = (GstHarnessCustomThread *) t; + ct->callback (ct, ct->data); +} +GST_HARNESS_STRESS_FUNC_END () + +GST_HARNESS_STRESS_FUNC_BEGIN (statechange, {}) +{ + GstClock *clock = gst_element_get_clock (t->h->element); + GstIterator *it; + gboolean done = FALSE; + + g_assert (gst_element_set_state (t->h->element, GST_STATE_NULL) == + GST_STATE_CHANGE_SUCCESS); + g_thread_yield (); + + it = gst_element_iterate_sink_pads (t->h->element); + while (!done) { + GValue item = G_VALUE_INIT; + switch (gst_iterator_next (it, &item)) { + case GST_ITERATOR_OK: + { + GstPad *sinkpad = g_value_get_object (&item); + GstPad *srcpad = gst_pad_get_peer (sinkpad); + if (srcpad != NULL) { + gst_pad_unlink (srcpad, sinkpad); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (srcpad); + } + g_value_reset (&item); + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync (it); + break; + case GST_ITERATOR_ERROR: + g_assert_not_reached (); + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + g_value_unset (&item); + } + gst_iterator_free (it); + + if (clock != NULL) { + gst_element_set_clock (t->h->element, clock); + gst_object_unref (clock); + } + g_assert (gst_element_set_state (t->h->element, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS); +} +GST_HARNESS_STRESS_FUNC_END () + +GST_HARNESS_STRESS_FUNC_BEGIN (buffer, { + GstHarnessPushBufferThread *pt = (GstHarnessPushBufferThread *) t; + gchar *sid; + /* Push stream start, caps and segment events */ + sid = g_strdup_printf ("%s-%p", GST_OBJECT_NAME (t->h->element), t->h); + g_assert (gst_pad_push_event (t->h->srcpad, + gst_event_new_stream_start (sid))); g_free (sid); + g_assert (gst_pad_push_event (t->h->srcpad, + gst_event_new_caps (pt->caps))); + g_assert (gst_pad_push_event (t->h->srcpad, + gst_event_new_segment (&pt->segment))); + } +) +{ + GstHarnessPushBufferThread *pt = (GstHarnessPushBufferThread *) t; + gst_harness_push (t->h, pt->func (t->h, pt->data)); +} +GST_HARNESS_STRESS_FUNC_END () + +GST_HARNESS_STRESS_FUNC_BEGIN (event, {}) +{ + GstHarnessPushEventThread *pet = (GstHarnessPushEventThread *) t; + gst_harness_push_event (t->h, gst_event_ref (pet->event)); +} +GST_HARNESS_STRESS_FUNC_END () + +GST_HARNESS_STRESS_FUNC_BEGIN (upstream_event, {}) +{ + GstHarnessPushEventThread *pet = (GstHarnessPushEventThread *) t; + gst_harness_push_upstream_event (t->h, gst_event_ref (pet->event)); +} +GST_HARNESS_STRESS_FUNC_END () + +GST_HARNESS_STRESS_FUNC_BEGIN (property, {}) +{ + GstHarnessPropThread *pt = (GstHarnessPropThread *) t; + GValue value = G_VALUE_INIT; + + g_object_set_property (G_OBJECT (t->h->element), pt->name, &pt->value); + + g_value_init (&value, G_VALUE_TYPE (&pt->value)); + g_object_get_property (G_OBJECT (t->h->element), pt->name, &value); + g_value_reset (&value); +} +GST_HARNESS_STRESS_FUNC_END () + +GST_HARNESS_STRESS_FUNC_BEGIN (requestpad, {}) +{ + GstHarnessReqPadThread *rpt = (GstHarnessReqPadThread *) t; + GstPad *reqpad; + + if (rpt->release) + gst_harness_requestpad_release_pads (rpt); + g_thread_yield (); + + reqpad = gst_element_request_pad (t->h->element, + rpt->templ, rpt->name, rpt->caps); + g_assert (reqpad != NULL); + + rpt->pads = g_slist_prepend (rpt->pads, reqpad); +} +GST_HARNESS_STRESS_FUNC_END () + +/** + * gst_harness_stress_thread_stop: + * @t: a #GstHarnessThread + * + * Stop the running #GstHarnessThread + * + * MT safe. + * + * Since: 1.6 + */ +guint +gst_harness_stress_thread_stop (GstHarnessThread * t) +{ + guint ret; + + g_return_val_if_fail (t != NULL, 0); + + ret = GST_HARNESS_THREAD_END (t); + g_ptr_array_remove (t->h->priv->stress, t); + return ret; +} + +/** + * gst_harness_stress_custom_start: (skip) + * @h: a #GstHarness + * @init: a #GFunc that is called initially and only once + * @callback: a #GFunc that is called as often as possible + * @data: a #gpointer with custom data to pass to the @callback function + * @sleep: a #gulong specifying how long to sleep in (microseconds) for + * each call to the @callback + * + * Start a custom stress-thread that will call your @callback for every + * iteration allowing you to do something nasty. + * + * MT safe. + * + * Returns: a #GstHarnessThread + * + * Since: 1.6 + */ +GstHarnessThread * +gst_harness_stress_custom_start (GstHarness * h, + GFunc init, GFunc callback, gpointer data, gulong sleep) +{ + GstHarnessCustomThread *t = g_slice_new0 (GstHarnessCustomThread); + gst_harness_thread_init (&t->t, + (GDestroyNotify) gst_harness_custom_thread_free, h, sleep); + + t->init = init; + t->callback = callback; + t->data = data; + + GST_HARNESS_THREAD_START (custom, t); + return &t->t; +} + +/** + * gst_harness_stress_statechange_start_full: (skip) + * @h: a #GstHarness + * @sleep: a #gulong specifying how long to sleep in (microseconds) for + * each state-change + * + * Change the state of your harnessed #GstElement from NULL to PLAYING and + * back again, only pausing for @sleep microseconds every time. + * + * MT safe. + * + * Returns: a #GstHarnessThread + * + * Since: 1.6 + */ +GstHarnessThread * +gst_harness_stress_statechange_start_full (GstHarness * h, gulong sleep) +{ + GstHarnessThread *t = g_slice_new0 (GstHarnessThread); + gst_harness_thread_init (t, + (GDestroyNotify) gst_harness_thread_free, h, sleep); + GST_HARNESS_THREAD_START (statechange, t); + return t; +} + +static GstBuffer * +gst_harness_ref_buffer (GstHarness * h, gpointer data) +{ + (void) h; + return gst_buffer_ref (GST_BUFFER_CAST (data)); +} + +/** + * gst_harness_stress_push_buffer_start_full: (skip) + * @h: a #GstHarness + * @caps: a #GstCaps for the #GstBuffer + * @segment: a #GstSegment + * @buf: a #GstBuffer to push + * @sleep: a #gulong specifying how long to sleep in (microseconds) for + * each call to gst_pad_push + * + * Push a #GstBuffer in intervals of @sleep microseconds. + * + * MT safe. + * + * Returns: a #GstHarnessThread + * + * Since: 1.6 + */ +GstHarnessThread * +gst_harness_stress_push_buffer_start_full (GstHarness * h, + GstCaps * caps, const GstSegment * segment, GstBuffer * buf, gulong sleep) +{ + return gst_harness_stress_push_buffer_with_cb_start_full (h, caps, segment, + gst_harness_ref_buffer, gst_buffer_ref (buf), + (GDestroyNotify) gst_buffer_unref, sleep); +} + +/** + * gst_harness_stress_push_buffer_with_cb_start_full: (skip) + * @h: a #GstHarness + * @caps: a #GstCaps for the #GstBuffer + * @segment: a #GstSegment + * @func: a #GstHarnessPrepareBuffer function called before every iteration + * to prepare / create a #GstBuffer for pushing + * @data: a #gpointer with data to the #GstHarnessPrepareBuffer function + * @notify: a #GDestroyNotify that is called for every push to allow cleaning + * up the #GstBuffer. (like gst_buffer_unref) + * @sleep: a #gulong specifying how long to sleep in (microseconds) for + * each call to gst_pad_push + * + * Push a #GstBuffer in intervals of @sleep microseconds. + * + * MT safe. + * + * Returns: a #GstHarnessThread + * + * Since: 1.6 + */ +GstHarnessThread * +gst_harness_stress_push_buffer_with_cb_start_full (GstHarness * h, + GstCaps * caps, const GstSegment * segment, + GstHarnessPrepareBuffer func, gpointer data, GDestroyNotify notify, + gulong sleep) +{ + GstHarnessPushBufferThread *t = g_slice_new0 (GstHarnessPushBufferThread); + gst_harness_thread_init (&t->t, + (GDestroyNotify) gst_harness_push_buffer_thread_free, h, sleep); + + gst_caps_replace (&t->caps, caps); + t->segment = *segment; + t->func = func; + t->data = data; + t->notify = notify; + + GST_HARNESS_THREAD_START (buffer, t); + return &t->t; +} + +/** + * gst_harness_stress_push_event_start_full: (skip) + * @h: a #GstHarness + * @event: a #GstEvent to push + * @sleep: a #gulong specifying how long to sleep in (microseconds) for + * each gst_event_push with @event + * + * Push the @event onto the harnessed #GstElement sinkpad in intervals of + * @sleep microseconds + * + * MT safe. + * + * Returns: a #GstHarnessThread + * + * Since: 1.6 + */ +GstHarnessThread * +gst_harness_stress_push_event_start_full (GstHarness * h, + GstEvent * event, gulong sleep) +{ + GstHarnessPushEventThread *t = g_slice_new0 (GstHarnessPushEventThread); + gst_harness_thread_init (&t->t, + (GDestroyNotify) gst_harness_push_event_thread_free, h, sleep); + + t->event = gst_event_ref (event); + GST_HARNESS_THREAD_START (event, t); + return &t->t; +} + +/** + * gst_harness_stress_push_upstream_event_start_full: (skip) + * @h: a #GstHarness + * @event: a #GstEvent to push + * @sleep: a #gulong specifying how long to sleep in (microseconds) for + * each gst_event_push with @event + * + * Push the @event onto the harnessed #GstElement srcpad in intervals of + * @sleep microseconds. + * Pushing events should generally be OOB events. + * If you need serialized events, you may use a custom stress thread which + * both pushes buffers and events. + * + * MT safe. + * + * Returns: a #GstHarnessThread + * + * Since: 1.6 + */ +GstHarnessThread * +gst_harness_stress_push_upstream_event_start_full (GstHarness * h, + GstEvent * event, gulong sleep) +{ + GstHarnessPushEventThread *t = g_slice_new0 (GstHarnessPushEventThread); + gst_harness_thread_init (&t->t, + (GDestroyNotify) gst_harness_push_event_thread_free, h, sleep); + + t->event = gst_event_ref (event); + GST_HARNESS_THREAD_START (upstream_event, t); + return &t->t; +} + +/** + * gst_harness_stress_property_start_full: (skip) + * @h: a #GstHarness + * @name: a #gchar specifying a property name + * @value: a #GValue to set the property to + * @sleep: a #gulong specifying how long to sleep in (microseconds) for + * each g_object_set with @name and @value + * + * Call g_object_set with @name and @value in intervals of @sleep microseconds + * + * MT safe. + * + * Returns: a #GstHarnessThread + * + * Since: 1.6 + */ +GstHarnessThread * +gst_harness_stress_property_start_full (GstHarness * h, + const gchar * name, const GValue * value, gulong sleep) +{ + GstHarnessPropThread *t = g_slice_new0 (GstHarnessPropThread); + gst_harness_thread_init (&t->t, + (GDestroyNotify) gst_harness_property_thread_free, h, sleep); + + t->name = g_strdup (name); + g_value_init (&t->value, G_VALUE_TYPE (value)); + g_value_copy (value, &t->value); + + GST_HARNESS_THREAD_START (property, t); + return &t->t; +} + +/** + * gst_harness_stress_requestpad_start_full: (skip) + * @h: a #GstHarness + * @templ: a #GstPadTemplate + * @name: a #gchar + * @caps: a #GstCaps + * @release: a #gboolean + * @sleep: a #gulong specifying how long to sleep in (microseconds) for + * each gst_element_request_pad + * + * Call gst_element_request_pad in intervals of @sleep microseconds + * + * MT safe. + * + * Returns: a #GstHarnessThread + * + * Since: 1.6 + */ +GstHarnessThread * +gst_harness_stress_requestpad_start_full (GstHarness * h, + GstPadTemplate * templ, const gchar * name, GstCaps * caps, + gboolean release, gulong sleep) +{ + GstHarnessReqPadThread *t = g_slice_new0 (GstHarnessReqPadThread); + gst_harness_thread_init (&t->t, + (GDestroyNotify) gst_harness_requestpad_thread_free, h, sleep); + + t->templ = gst_object_ref (templ); + t->name = g_strdup (name); + gst_caps_replace (&t->caps, caps); + t->release = release; + + GST_HARNESS_THREAD_START (requestpad, t); + return &t->t; +} diff --git a/libs/gst/check/gstharness.h b/libs/gst/check/gstharness.h new file mode 100644 index 0000000000..f56adba037 --- /dev/null +++ b/libs/gst/check/gstharness.h @@ -0,0 +1,197 @@ +/* GstHarness - A test-harness for GStreamer testing + * + * Copyright (C) 2012-2015 Pexip + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_HARNESS_H__ +#define __GST_HARNESS_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstHarness GstHarness; +typedef struct _GstHarnessPrivate GstHarnessPrivate; +typedef struct _GstHarnessThread GstHarnessThread; + +struct _GstHarness { + GstElement * element; + + GstPad * srcpad; + GstPad * sinkpad; + + GstHarness * src_harness; + GstHarness * sink_harness; + + GstHarnessPrivate * priv; +}; + +/* Harness creation */ +GstHarness * gst_harness_new_full (GstElement * element, + GstStaticPadTemplate * hsrc, const gchar * sinkpad, + GstStaticPadTemplate * hsink, const gchar * srcpad); +GstHarness * gst_harness_new_with_element (GstElement * element, + const gchar * sinkpad, const gchar * srcpad); +GstHarness * gst_harness_new_with_padnames (const gchar * element_name, + const gchar * sinkpad, const gchar * srcpad); +GstHarness * gst_harness_new_with_templates (const gchar * element_name, + GstStaticPadTemplate * hsrc, GstStaticPadTemplate * hsink); +GstHarness * gst_harness_new (const gchar * element_name); +GstHarness * gst_harness_new_parse (const gchar * launchline); +void gst_harness_teardown (GstHarness * h); + +void gst_harness_add_element_src_pad (GstHarness * h, GstPad * srcpad); +void gst_harness_add_element_sink_pad (GstHarness * h, GstPad * sinkpad); + +/* Caps Functions */ +void gst_harness_set_src_caps (GstHarness * h, GstCaps * caps); +void gst_harness_set_sink_caps (GstHarness * h, GstCaps * caps); +void gst_harness_set_caps (GstHarness * h, GstCaps * in, GstCaps * out); +void gst_harness_set_src_caps_str (GstHarness * h, const gchar * str); +void gst_harness_set_sink_caps_str (GstHarness * h, const gchar * str); +void gst_harness_set_caps_str (GstHarness * h, + const gchar * in, const gchar * out); + +/* Clock Functions */ +void gst_harness_use_systemclock (GstHarness * h); +void gst_harness_use_testclock (GstHarness * h); +GstTestClock * gst_harness_get_testclock (GstHarness * h); +gboolean gst_harness_set_time (GstHarness * h, GstClockTime time); +gboolean gst_harness_wait_for_clock_id_waits (GstHarness * h, + guint waits, guint timeout); +gboolean gst_harness_crank_single_clock_wait (GstHarness * h); +gboolean gst_harness_crank_multiple_clock_waits (GstHarness * h, + guint waits); + +void gst_harness_play (GstHarness * h); +void gst_harness_set_blocking_push_mode (GstHarness * h); + +/* buffers */ +GstBuffer * gst_harness_create_buffer (GstHarness * h, gsize size); +GstFlowReturn gst_harness_push (GstHarness * h, GstBuffer * buffer); +GstBuffer * gst_harness_pull (GstHarness * h); +GstBuffer * gst_harness_try_pull (GstHarness * h); +GstBuffer * gst_harness_push_and_pull (GstHarness * h, GstBuffer * buffer); +guint gst_harness_buffers_received (GstHarness * h); +guint gst_harness_buffers_in_queue (GstHarness * h); +void gst_harness_set_drop_buffers (GstHarness * h, gboolean drop_buffers); +void gst_harness_dump_to_file (GstHarness * h, const gchar * filename); +GstClockTime gst_harness_get_last_pushed_timestamp (GstHarness * h); + +/* downstream events */ +gboolean gst_harness_push_event (GstHarness * h, GstEvent * event); +GstEvent * gst_harness_pull_event (GstHarness * h); +GstEvent * gst_harness_try_pull_event (GstHarness * h); +guint gst_harness_events_received (GstHarness * h); +guint gst_harness_events_in_queue (GstHarness * h); + +/* upstream events */ +gboolean gst_harness_push_upstream_event (GstHarness * h, GstEvent * event); +GstEvent * gst_harness_pull_upstream_event (GstHarness * h); +GstEvent * gst_harness_try_pull_upstream_event (GstHarness * h); +guint gst_harness_upstream_events_received (GstHarness * h); +guint gst_harness_upstream_events_in_queue (GstHarness * h); + +/* latency */ +GstClockTime gst_harness_query_latency (GstHarness * h); +void gst_harness_set_upstream_latency (GstHarness * h, GstClockTime latency); + +/* allocator and allocation params */ +void gst_harness_set_propose_allocator (GstHarness * h, GstAllocator * allocator, + const GstAllocationParams * params); +void gst_harness_get_allocator (GstHarness * h, GstAllocator ** allocator, + GstAllocationParams * params); + +/* src-harness */ +void gst_harness_add_src (GstHarness * h, + const gchar * src_element_name, gboolean has_clock_wait); +void gst_harness_add_src_parse (GstHarness * h, + const gchar * launchline, gboolean has_clock_wait); +GstFlowReturn gst_harness_push_from_src (GstHarness * h); +GstFlowReturn gst_harness_src_crank_and_push_many (GstHarness * h, + gint cranks, gint pushes); +gboolean gst_harness_src_push_event (GstHarness * h); + +/* sink-harness */ +void gst_harness_add_sink (GstHarness * h, + const gchar * sink_element_name); +void gst_harness_add_sink_parse (GstHarness * h, + const gchar * launchline); +GstFlowReturn gst_harness_push_to_sink (GstHarness * h); +GstFlowReturn gst_harness_sink_push_many (GstHarness * h, gint pushes); + +/* convenience functions */ +GstElement * gst_harness_find_element (GstHarness * h, + const gchar * element_name); +void gst_harness_set (GstHarness * h, + const gchar * element_name, const gchar * first_property_name, ...); +void gst_harness_get (GstHarness * h, + const gchar * element_name, const gchar * first_property_name, ...); +void gst_harness_add_probe (GstHarness * h, + const gchar * element_name, const gchar * pad_name, GstPadProbeType mask, + GstPadProbeCallback callback, gpointer user_data, + GDestroyNotify destroy_data); + +/* Stress */ +guint gst_harness_stress_thread_stop (GstHarnessThread * t); +GstHarnessThread * gst_harness_stress_custom_start (GstHarness * h, + GFunc init, GFunc callback, gpointer data, gulong sleep); + +#define gst_harness_stress_statechange_start(h) \ + gst_harness_stress_statechange_start_full (h, G_USEC_PER_SEC / 100) +GstHarnessThread * gst_harness_stress_statechange_start_full (GstHarness * h, + gulong sleep); + +#define gst_harness_stress_push_buffer_start(h, c, s, b) \ + gst_harness_stress_push_buffer_start_full (h, c, s, b, 0) +GstHarnessThread * gst_harness_stress_push_buffer_start_full (GstHarness * h, + GstCaps * caps, const GstSegment * segment, GstBuffer * buf, gulong sleep); + +typedef GstBuffer * (*GstHarnessPrepareBuffer) (GstHarness * h, gpointer data); +#define gst_harness_stress_push_buffer_with_cb_start(h, c, s, f, d, n) \ + gst_harness_stress_push_buffer_with_cb_start_full (h, c, s, f, d, n, 0) +GstHarnessThread * gst_harness_stress_push_buffer_with_cb_start_full ( + GstHarness * h, GstCaps * caps, const GstSegment * segment, + GstHarnessPrepareBuffer func, gpointer data, GDestroyNotify notify, + gulong sleep); + +#define gst_harness_stress_push_event_start(h, e) \ + gst_harness_stress_push_event_start_full (h, e, 0) +GstHarnessThread * gst_harness_stress_push_event_start_full (GstHarness * h, + GstEvent * event, gulong sleep); + +#define gst_harness_stress_send_upstream_event_start(h, e) \ + gst_harness_stress_push_upstream_event_start_full (h, e, 0) +GstHarnessThread * gst_harness_stress_push_upstream_event_start_full ( + GstHarness * h, GstEvent * event, gulong sleep); + +#define gst_harness_stress_property_start(h, n, v) \ + gst_harness_stress_property_start_full (h, n, v, G_USEC_PER_SEC / 1000) +GstHarnessThread * gst_harness_stress_property_start_full (GstHarness * h, + const gchar * name, const GValue * value, gulong sleep); + +#define gst_harness_stress_requestpad_start(h, t, n, c, r) \ + gst_harness_stress_requestpad_start_full (h, t, n, c, r, G_USEC_PER_SEC / 100) +GstHarnessThread * gst_harness_stress_requestpad_start_full (GstHarness * h, + GstPadTemplate * templ, const gchar * name, GstCaps * caps, + gboolean release, gulong sleep); + +G_END_DECLS + +#endif /* __GST_HARNESS_H__ */