gstreamer/libs/gst/check/gstharness.c
Mikhail Fludkov 06ae41e0b2 harness: fix the race in blocking push mode
Depending on when gst_harness_pull was called - before the buffer reached
gst_harness_chain or after we can get different behaviors of the test
with enabled blocking push mode. The fix makes the behavior always the
same. In pull function we get the buffer first, thus making sure
gst_harness_chain waits for the signal, and emitting the signal after.

https://bugzilla.gnome.org/show_bug.cgi?id=761931
2016-02-12 12:32:22 +00:00

3097 lines
82 KiB
C

/* GstHarness - A test-harness for GStreamer testing
*
* Copyright (C) 2012-2015 Pexip <pexip.com>
*
* 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 meant to make writing unit test for GStreamer much easier.
* It can be thought of as a way of treating a #GstElement as a black box,
* deterministically feeding it data, and controlling what data it outputs.
*
* The basic structure of #GstHarness is two "floating" #GstPads that connect
* to the harnessed #GstElement src and sink #GstPads like so:
*
* <programlisting>
* __________________________
* _____ | _____ _____ | _____
* | | | | | | | | | |
* | src |--+-| sink| Element | src |-+--| sink|
* |_____| | |_____| |_____| | |_____|
* |__________________________|
*
* </programlisting>
*
* 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 caps sets.
*
* 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().
*
* <example>
* <title>A simple buffer-in buffer-out example</title>
* <programlisting language="c">
* #include &lt;gst/gst.h&gt;
* #include &lt;gst/check/gstharness.h&gt;
* 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);
*
* </programlisting>
* </example>
*
* 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:
*
* <example>
* <programlisting language="c">
* GstHarness * h = gst_harness_new (h, "vp8dec");
* gst_harness_add_src_parse (h, "videotestsrc is-live=1 ! vp8enc", TRUE);
* </programlisting>
* </example>
*
* and then feeding it data with:
*
* <example>
* <programlisting language="c">
* gst_harness_push_from_src (h);
* </programlisting>
* </example>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* we have code with side effects in asserts, so make sure they are active */
#ifdef G_DISABLE_ASSERT
#error "GstHarness must be compiled with G_DISABLE_ASSERT undefined"
#endif
#include "gstharness.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
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;
gboolean forwarding;
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 (priv->forwarding && 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, &params);
} else {
allocator = NULL;
gst_allocation_params_init (&params);
}
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, &params);
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;
if (priv->sink_caps) {
caps = gst_caps_ref (priv->sink_caps);
} else {
caps = gst_pad_get_pad_template_caps (pad);
}
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->forwarding && 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;
guint size;
gst_query_parse_allocation (query, &caps, &need_pool);
/* FIXME: Can this be removed? */
size = gst_query_get_n_allocation_params (query);
g_assert_cmpuint (0, ==, size);
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;
if (priv->src_caps) {
caps = gst_caps_ref (priv->src_caps);
} else {
caps = gst_pad_get_pad_template_caps (pad);
}
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;
GST_OBJECT_LOCK (h->element);
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)++;
}
GST_OBJECT_UNLOCK (h->element);
}
static guint
gst_harness_element_unref (GstHarness * h)
{
guint *data;
guint ret;
GST_OBJECT_LOCK (h->element);
data = g_object_get_data (G_OBJECT (h->element), HARNESS_REF);
g_assert (data != NULL);
(*data)--;
ret = *data;
GST_OBJECT_UNLOCK (h->element);
return ret;
}
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);
GstPadLinkReturn link;
if (srcpad == NULL)
srcpad = gst_element_get_request_pad (h->element, element_srcpad_name);
g_assert (srcpad);
link = gst_pad_link (srcpad, h->sinkpad);
g_assert_cmpint (link, ==, 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);
GstPadLinkReturn link;
if (sinkpad == NULL)
sinkpad = gst_element_get_request_pad (h->element, element_sinkpad_name);
g_assert (sinkpad);
link = gst_pad_link (h->srcpad, sinkpad);
g_assert_cmpint (link, ==, 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);
g_assert (h->srcpad == NULL);
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);
g_assert (h->sinkpad == NULL);
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
check_element_type (GstElement * element, gboolean * has_sinkpad,
gboolean * has_srcpad)
{
GstElementClass *element_class = GST_ELEMENT_GET_CLASS (element);
const GList *tmpl_list;
*has_srcpad = element->numsrcpads > 0;
*has_sinkpad = element->numsinkpads > 0;
tmpl_list = gst_element_class_get_pad_template_list (element_class);
while (tmpl_list) {
GstPadTemplate *pad_tmpl = (GstPadTemplate *) tmpl_list->data;
tmpl_list = g_list_next (tmpl_list);
if (GST_PAD_TEMPLATE_DIRECTION (pad_tmpl) == GST_PAD_SRC)
*has_srcpad |= TRUE;
if (GST_PAD_TEMPLATE_DIRECTION (pad_tmpl) == GST_PAD_SINK)
*has_sinkpad |= TRUE;
}
}
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;
gboolean is_request;
if (pad == NULL)
return FALSE;
temp = gst_pad_get_pad_template (pad);
if (temp == NULL)
return FALSE;
is_request = GST_PAD_TEMPLATE_PRESENCE (temp) == GST_PAD_REQUEST;
gst_object_unref (temp);
return is_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.
* @element_sinkpad_name: (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.
* @element_srcpad_name: (allow-none): a #gchar with the name of the element
* srcpad that is then linked to the harness sinkpad, similar to the
* @element_sinkpad_name.
*
* 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 * element_sinkpad_name,
GstStaticPadTemplate * hsink, const gchar * element_srcpad_name)
{
GstHarness *h;
GstHarnessPrivate *priv;
gboolean has_sinkpad, has_srcpad;
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);
check_element_type (element, &has_sinkpad, &has_srcpad);
/* setup the loose srcpad linked to the element sinkpad */
if (has_sinkpad)
gst_harness_setup_src_pad (h, hsrc, element_sinkpad_name);
/* setup the loose sinkpad linked to the element srcpad */
if (has_srcpad)
gst_harness_setup_sink_pad (h, hsink, element_srcpad_name);
/* as a harness sink, we should not need sync and async */
if (has_sinkpad && !has_srcpad)
turn_async_and_sync_off (h->element);
if (h->srcpad != NULL) {
gboolean handled;
gchar *stream_id = g_strdup_printf ("%s-%p",
GST_OBJECT_NAME (h->element), h);
handled = gst_pad_push_event (h->srcpad,
gst_event_new_stream_start (stream_id));
g_assert (handled);
g_free (stream_id);
}
/* don't start sources, they start producing data! */
if (has_sinkpad)
gst_harness_play (h);
gst_harness_element_ref (h);
GST_DEBUG_OBJECT (h, "created new harness %p "
"with element_srcpad_name (%p, %s, %s) and element_sinkpad_name (%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);
/* we have forwarding on as a default */
gst_harness_set_forwarding (h, TRUE);
return h;
}
/**
* gst_harness_new_with_element: (skip)
* @element: a #GstElement to attach the harness to (transfer none)
* @element_sinkpad_name: (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
* @element_srcpad_name: (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 * element_sinkpad_name, const gchar * element_srcpad_name)
{
return gst_harness_new_full (element,
&hsrctemplate, element_sinkpad_name, &hsinktemplate, element_srcpad_name);
}
/**
* gst_harness_new_with_padnames: (skip)
* @element_name: a #gchar describing the #GstElement name
* @element_sinkpad_name: (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
* @element_srcpad_name: (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 like 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 * element_sinkpad_name, const gchar * element_srcpad_name)
{
GstHarness *h;
GstElement *element = gst_element_factory_make (element_name, NULL);
g_assert (element != NULL);
h = gst_harness_new_with_element (element, element_sinkpad_name,
element_srcpad_name);
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);
}
gst_object_replace ((GstObject **) & priv->sink_forward_pad, NULL);
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);
}
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) {
gboolean state_change;
GstState state, pending;
state_change = gst_element_set_state (h->element, GST_STATE_NULL);
g_assert (state_change == GST_STATE_CHANGE_SUCCESS);
state_change = gst_element_get_state (h->element, &state, &pending, 0);
g_assert (state_change == 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;
GstPadLinkReturn link;
if (h->sinkpad == NULL)
gst_harness_setup_sink_pad (h, &hsinktemplate, NULL);
link = gst_pad_link (srcpad, h->sinkpad);
g_assert_cmpint (link, ==, 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;
GstPadLinkReturn link;
if (h->srcpad == NULL)
gst_harness_setup_src_pad (h, &hsrctemplate, NULL);
link = gst_pad_link (h->srcpad, sinkpad);
g_assert_cmpint (link, ==, 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;
gboolean handled;
handled = gst_pad_push_event (h->srcpad, gst_event_new_caps (caps));
g_assert (handled);
gst_caps_take (&priv->src_caps, caps);
gst_segment_init (&segment, GST_FORMAT_TIME);
handled = 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 must call gst_harness_play() explicitly.
*
* MT safe.
*
* Since: 1.6
*/
void
gst_harness_play (GstHarness * h)
{
GstState state, pending;
gboolean state_change;
state_change = gst_element_set_state (h->element, GST_STATE_PLAYING);
g_assert_cmpint (GST_STATE_CHANGE_SUCCESS, ==, state_change);
state_change = gst_element_get_state (h->element, &state, &pending, 0);
g_assert_cmpint (GST_STATE_CHANGE_SUCCESS, ==, state_change);
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_set_forwarding:
* @h: a #GstHarness
* @forwarding: a #gboolean to enable/disable forwarding
*
* As a convenience, a src-harness will forward %GST_EVENT_STREAM_START,
* %GST_EVENT_CAPS and %GST_EVENT_SEGMENT to the main-harness if forwarding
* is enabled, and forward any sticky-events from the main-harness to
* the sink-harness. It will also forward the %GST_QUERY_ALLOCATION.
*
* If forwarding is disabled, the user will have to either manually push
* these events from the src-harness using gst_harness_src_push_event(), or
* create and push them manually. While this will allow full control and
* inspection of these events, for the most cases having forwarding enabled
* will be sufficient when writing a test where the src-harness' main function
* is providing data for the main-harness.
*
* Forwarding is enabled by default.
*
* MT safe.
*
* Since: 1.6
*/
void
gst_harness_set_forwarding (GstHarness * h, gboolean forwarding)
{
GstHarnessPrivate *priv = h->priv;
priv->forwarding = forwarding;
if (h->src_harness)
gst_harness_set_forwarding (h->src_harness, forwarding);
if (h->sink_harness)
gst_harness_set_forwarding (h->sink_harness, forwarding);
}
/**
* 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 #GstAllocationParams
*
* 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;
GstFlowReturn flow;
if (gst_pad_check_reconfigure (h->srcpad))
gst_harness_negotiate (h);
if (priv->pool) {
flow = gst_buffer_pool_acquire_buffer (priv->pool, &ret, NULL);
g_assert_cmpint (flow, ==, 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;
GstBuffer *buf = (GstBuffer *) g_async_queue_timeout_pop (priv->buffer_queue,
G_USEC_PER_SEC * 60);
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 buf;
}
/**
* 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;
GstBuffer *buf = (GstBuffer *) g_async_queue_try_pop (priv->buffer_queue);
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 buf;
}
/**
* 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 #GstAllocationParams 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_set_propose_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;
}
/**
* gst_harness_add_src_harness:
* @h: a #GstHarness
* @src_harness: (transfer full): a #GstHarness to be added as a src-harness.
* @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_harness (GstHarness * h,
GstHarness * src_harness, gboolean has_clock_wait)
{
if (h->src_harness)
gst_harness_teardown (h->src_harness);
h->src_harness = src_harness;
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_set_forwarding (h->src_harness, h->priv->forwarding);
}
/**
* 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.
*
* Similar to gst_harness_add_src_harness, this is a convenience to
* directly create a src-harness using the @src_element_name name specified.
*
* MT safe.
*
* Since: 1.6
*/
void
gst_harness_add_src (GstHarness * h,
const gchar * src_element_name, gboolean has_clock_wait)
{
GstHarness *src_harness = gst_harness_new (src_element_name);
gst_harness_add_src_harness (h, src_harness, 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)
{
GstHarness *src_harness = gst_harness_new_parse (launchline);
gst_harness_add_src_harness (h, src_harness, 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;
gboolean crank;
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) {
crank = gst_harness_crank_single_clock_wait (h->src_harness);
g_assert (crank);
}
buf = gst_harness_pull (h->src_harness);
g_assert (buf != 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;
gboolean crank;
g_assert (h->src_harness);
gst_harness_play (h->src_harness);
for (int i = 0; i < cranks; i++) {
crank = gst_harness_crank_single_clock_wait (h->src_harness);
g_assert (crank);
}
for (int i = 0; i < pushes; i++) {
GstBuffer *buf;
buf = gst_harness_pull (h->src_harness);
g_assert (buf != 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));
}
static gboolean
forward_sticky_events (GstPad * pad, GstEvent ** ev, gpointer user_data)
{
GstHarness *h = user_data;
return gst_pad_push_event (h->priv->sink_forward_pad, gst_event_ref (*ev));
}
/**
* gst_harness_add_sink_harness:
* @h: a #GstHarness
* @sink_harness: (transfer full): a #GstHarness to be added as a sink-harness.
*
* 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_harness (GstHarness * h, GstHarness * sink_harness)
{
GstHarnessPrivate *priv = h->priv;
if (h->sink_harness) {
gst_object_replace ((GstObject **) &priv->sink_forward_pad, NULL);
gst_harness_teardown (h->sink_harness);
}
h->sink_harness = sink_harness;
priv->sink_forward_pad = gst_object_ref (h->sink_harness->srcpad);
gst_harness_use_testclock (h->sink_harness);
if (priv->forwarding && h->sinkpad)
gst_pad_sticky_events_foreach (h->sinkpad, forward_sticky_events, h);
gst_harness_set_forwarding (h->sink_harness, priv->forwarding);
}
/**
* gst_harness_add_sink:
* @h: a #GstHarness
* @sink_element_name: a #gchar with the name of a #GstElement
*
* Similar to gst_harness_add_sink_harness, this is a convenience to
* directly create a sink-harness using the @sink_element_name name specified.
*
* MT safe.
*
* Since: 1.6
*/
void
gst_harness_add_sink (GstHarness * h, const gchar * sink_element_name)
{
GstHarness *sink_harness = gst_harness_new (sink_element_name);
gst_harness_add_sink_harness (h, sink_harness);
}
/**
* 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)
{
GstHarness *sink_harness = gst_harness_new_parse (launchline);
gst_harness_add_sink_harness (h, sink_harness);
}
/**
* 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);
buf = gst_harness_pull (h);
g_assert (buf != 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
* @...: value for the first property, followed optionally by more
* name/value pairs, followed by %NULL
*
* 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
* @...: return location for the first property, followed optionally by more
* name/return location pairs, followed by %NULL
*
* 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;
GstHarnessPrepareBufferFunc 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)))
static void
gst_harness_stress_free (GstHarnessThread * t)
{
if (t != NULL && t->freefunc != NULL)
t->freefunc (t);
}
static gpointer
gst_harness_stress_custom_func (GstHarnessThread * t)
{
GstHarnessCustomThread *ct = (GstHarnessCustomThread *) t;
guint count = 0;
if (ct->init != NULL)
ct->init (ct, ct->data);
while (t->running) {
ct->callback (ct, ct->data);
count++;
g_usleep (t->sleep);
}
return GUINT_TO_POINTER (count);
}
static gpointer
gst_harness_stress_statechange_func (GstHarnessThread * t)
{
guint count = 0;
while (t->running) {
GstClock *clock = gst_element_get_clock (t->h->element);
GstIterator *it;
gboolean done = FALSE;
gboolean change;
change = gst_element_set_state (t->h->element, GST_STATE_NULL);
g_assert (change == 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);
}
change = gst_element_set_state (t->h->element, GST_STATE_PLAYING);
g_assert (change == GST_STATE_CHANGE_SUCCESS);
count++;
g_usleep (t->sleep);
}
return GUINT_TO_POINTER (count);
}
static gpointer
gst_harness_stress_buffer_func (GstHarnessThread * t)
{
GstHarnessPushBufferThread *pt = (GstHarnessPushBufferThread *) t;
guint count = 0;
gchar *sid;
gboolean handled;
/* Push stream start, caps and segment events */
sid = g_strdup_printf ("%s-%p", GST_OBJECT_NAME (t->h->element), t->h);
handled = gst_pad_push_event (t->h->srcpad, gst_event_new_stream_start (sid));
g_assert (handled);
g_free (sid);
handled = gst_pad_push_event (t->h->srcpad, gst_event_new_caps (pt->caps));
g_assert (handled);
handled = gst_pad_push_event (t->h->srcpad,
gst_event_new_segment (&pt->segment));
g_assert (handled);
while (t->running) {
gst_harness_push (t->h, pt->func (t->h, pt->data));
count++;
g_usleep (t->sleep);
}
return GUINT_TO_POINTER (count);
}
static gpointer
gst_harness_stress_event_func (GstHarnessThread * t)
{
GstHarnessPushEventThread *pet = (GstHarnessPushEventThread *) t;
guint count = 0;
while (t->running) {
gst_harness_push_event (t->h, gst_event_ref (pet->event));
count++;
g_usleep (t->sleep);
}
return GUINT_TO_POINTER (count);
}
static gpointer
gst_harness_stress_upstream_event_func (GstHarnessThread * t)
{
GstHarnessPushEventThread *pet = (GstHarnessPushEventThread *) t;
guint count = 0;
while (t->running) {
gst_harness_push_upstream_event (t->h, gst_event_ref (pet->event));
count++;
g_usleep (t->sleep);
}
return GUINT_TO_POINTER (count);
}
static gpointer
gst_harness_stress_property_func (GstHarnessThread * t)
{
GstHarnessPropThread *pt = (GstHarnessPropThread *) t;
guint count = 0;
while (t->running) {
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);
count++;
g_usleep (t->sleep);
}
return GUINT_TO_POINTER (count);
}
static gpointer
gst_harness_stress_requestpad_func (GstHarnessThread * t)
{
GstHarnessReqPadThread *rpt = (GstHarnessReqPadThread *) t;
guint count = 0;
while (t->running) {
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);
count++;
g_usleep (t->sleep);
}
return GUINT_TO_POINTER (count);
}
/**
* 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: (allow-none): 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 #GstHarnessPrepareBufferFunc function called before every iteration
* to prepare / create a #GstBuffer for pushing
* @data: a #gpointer with data to the #GstHarnessPrepareBufferFunc function
* @notify: a #GDestroyNotify that is called when thread is stopped
* @sleep: a #gulong specifying how long to sleep in (microseconds) for
* each call to gst_pad_push
*
* Push a #GstBuffer returned by @func 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,
GstHarnessPrepareBufferFunc 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.
*
* 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;
}