Don't take extra ref during calling done() from 'stream-selection' Mark as done actions that are completed immediately Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9042>
9287 lines
291 KiB
C
9287 lines
291 KiB
C
/* GStreamer
|
||
|
||
*
|
||
* Copyright (C) 2013 Collabora Ltd.
|
||
* Author: Thibault Saunier <thibault.saunier@collabora.com>
|
||
* Copyright (C) 2018-2022 Igalia S.L
|
||
* Author: Thibault Saunier <tsaunier@igalia.com>
|
||
|
||
*
|
||
* gst-validate-scenario.c - Validate Scenario class
|
||
*
|
||
* 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.1 of the License, or (at your option) any later version.
|
||
*
|
||
* This library is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* 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:gst-validate-scenario
|
||
* @title: GstValidateScenario
|
||
* @short_description: A GstValidateScenario represents a set of actions to be executed on a pipeline.
|
||
*
|
||
* A #GstValidateScenario represents the scenario that will be executed on a #GstPipeline.
|
||
* It is basically an ordered list of #GstValidateAction that will be executed during the
|
||
* execution of the pipeline.
|
||
*
|
||
* Possible configurations (see [GST_VALIDATE_CONFIG](gst-validate-environment-variables.md)):
|
||
* * scenario-action-execution-interval: Sets the interval in
|
||
* milliseconds (1/1000ths of a second), between which actions
|
||
* will be executed, setting it to 0 means "execute in idle".
|
||
* The default value is 10ms.
|
||
*/
|
||
|
||
#ifdef HAVE_CONFIG_H
|
||
#include "config.h"
|
||
#endif
|
||
|
||
#include <stdio.h>
|
||
#include <gst/gst.h>
|
||
#include <gio/gio.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <math.h>
|
||
|
||
#include <gst/check/gsttestclock.h>
|
||
#include "gst-validate-internal.h"
|
||
#include "gst-validate-scenario.h"
|
||
#include "gst-validate-reporter.h"
|
||
#include "gst-validate-report.h"
|
||
#include "gst-validate-utils.h"
|
||
#include "gst-validate-internal.h"
|
||
#include "validate.h"
|
||
#include "validate-resources.h"
|
||
#include <gst/controller/controller.h>
|
||
#include <gst/app/app.h>
|
||
#include <gst/validate/gst-validate-override.h>
|
||
#include <gst/validate/gst-validate-override-registry.h>
|
||
#include <gst/validate/gst-validate-pipeline-monitor.h>
|
||
|
||
extern gboolean run_http_request (const GstStructure * args, GError ** error);
|
||
|
||
#define GST_VALIDATE_SCENARIO_DIRECTORY "scenarios"
|
||
|
||
#define DEFAULT_SEEK_TOLERANCE (1 * GST_MSECOND) /* tolerance seek interval
|
||
TODO make it overridable */
|
||
|
||
GST_DEBUG_CATEGORY_STATIC (gst_validate_scenario_debug);
|
||
#undef GST_CAT_DEFAULT
|
||
#define GST_CAT_DEFAULT gst_validate_scenario_debug
|
||
|
||
#define REGISTER_ACTION_TYPE(_tname, _function, _params, _desc, _is_config) G_STMT_START { \
|
||
type = gst_validate_register_action_type ((_tname), "core", (_function), (_params), (_desc), (_is_config)); \
|
||
} G_STMT_END
|
||
|
||
#define ACTION_EXPECTED_STREAM_QUARK g_quark_from_static_string ("ACTION_EXPECTED_STREAM_QUARK")
|
||
|
||
#define SCENARIO_LOCK(scenario) G_STMT_START { \
|
||
GST_LOG_OBJECT (scenario, "About to lock %p", &scenario->priv->lock); \
|
||
g_mutex_lock(&scenario->priv->lock); \
|
||
GST_LOG_OBJECT (scenario, "Acquired lock %p", &scenario->priv->lock); \
|
||
} G_STMT_END
|
||
|
||
#define SCENARIO_UNLOCK(scenario) G_STMT_START { \
|
||
GST_LOG_OBJECT (scenario, "About to unlock %p", &scenario->priv->lock); \
|
||
g_mutex_unlock(&scenario->priv->lock); \
|
||
GST_LOG_OBJECT (scenario, "unlocked %p", &scenario->priv->lock); \
|
||
} G_STMT_END
|
||
|
||
#define DECLARE_AND_GET_PIPELINE(s,a) \
|
||
GstElement * pipeline = gst_validate_scenario_get_pipeline (s); \
|
||
if (pipeline == NULL) { \
|
||
GST_VALIDATE_REPORT_ACTION (s, a, SCENARIO_ACTION_EXECUTION_ERROR, \
|
||
"Can't execute a '%s' action after the pipeline " \
|
||
"has been destroyed.", a->type); \
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \
|
||
}
|
||
|
||
#ifdef G_HAVE_ISO_VARARGS
|
||
#define REPORT_UNLESS(condition, errpoint, ...) \
|
||
G_STMT_START { \
|
||
if (!(condition)) { \
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \
|
||
gst_validate_report_action(GST_VALIDATE_REPORTER(scenario), action, \
|
||
SCENARIO_ACTION_EXECUTION_ERROR, \
|
||
__VA_ARGS__); \
|
||
goto errpoint; \
|
||
} \
|
||
} \
|
||
G_STMT_END
|
||
#elif defined(G_HAVE_GNUC_VARARGS)
|
||
#define REPORT_UNLESS(condition, errpoint, args...) \
|
||
G_STMT_START { \
|
||
if (!(condition)) { \
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED; \
|
||
gst_validate_report_action(GST_VALIDATE_REPORTER(scenario), action, \
|
||
SCENARIO_ACTION_EXECUTION_ERROR, ##args); \
|
||
goto errpoint; \
|
||
} \
|
||
} \
|
||
G_STMT_END
|
||
#endif
|
||
|
||
enum
|
||
{
|
||
PROP_0,
|
||
PROP_RUNNER,
|
||
PROP_HANDLES_STATE,
|
||
PROP_EXECUTE_ON_IDLE,
|
||
PROP_LAST
|
||
};
|
||
|
||
enum
|
||
{
|
||
DONE,
|
||
ACTION_DONE,
|
||
STOPPING,
|
||
LAST_SIGNAL
|
||
};
|
||
|
||
static guint scenario_signals[LAST_SIGNAL] = { 0 };
|
||
|
||
static GList *action_types = NULL;
|
||
static void gst_validate_scenario_dispose (GObject * object);
|
||
static void gst_validate_scenario_finalize (GObject * object);
|
||
static GstValidateActionType *_find_action_type (const gchar * type_name);
|
||
static GstValidateExecuteActionReturn
|
||
_fill_action (GstValidateScenario * scenario, GstValidateAction * action,
|
||
GstStructure * structure, gboolean add_to_lists);
|
||
static gboolean _action_set_done (GstValidateAction * action);
|
||
static GList *_find_elements_defined_in_action (GstValidateScenario * scenario,
|
||
GstValidateAction * action);
|
||
static GstValidateAction *gst_validate_create_subaction (GstValidateScenario *
|
||
scenario, GstStructure * lvariables, GstValidateAction * action,
|
||
GstStructure * nstruct, gint it, gint max);
|
||
|
||
/* GstValidateSinkInformation tracks information for all sinks in the pipeline */
|
||
typedef struct
|
||
{
|
||
GstElement *sink; /* The sink element tracked */
|
||
guint32 segment_seqnum; /* The latest segment seqnum. GST_SEQNUM_INVALID if none */
|
||
GstSegment segment; /* The latest segment */
|
||
} GstValidateSinkInformation;
|
||
|
||
/* GstValidateSeekInformation tracks:
|
||
* * The values used in the seek
|
||
* * The seqnum used in the seek event
|
||
* * The validate action to which it relates
|
||
*/
|
||
typedef struct
|
||
{
|
||
guint32 seqnum; /* seqnum of the seek event */
|
||
|
||
/* Seek values */
|
||
gdouble rate;
|
||
GstFormat format;
|
||
GstSeekFlags flags;
|
||
GstSeekType start_type, stop_type;
|
||
gint64 start, stop;
|
||
|
||
/* The action corresponding to this seek */
|
||
GstValidateAction *action;
|
||
} GstValidateSeekInformation;
|
||
|
||
typedef struct
|
||
{
|
||
GSubprocess *subprocess;
|
||
gint port;
|
||
} HTTPServer;
|
||
|
||
/* GstValidateScenario is not really thread safe and
|
||
* everything should be done from the thread GstValidate
|
||
* was inited from, unless stated otherwise.
|
||
*/
|
||
struct _GstValidateScenarioPrivate
|
||
{
|
||
GstBus *bus;
|
||
gboolean execute_on_idle;
|
||
|
||
GMutex lock;
|
||
|
||
GList *actions;
|
||
GList *non_blocking_running_actions; /* MT safe. Protected with SCENARIO_LOCK */
|
||
GList *on_addition_actions; /* MT safe. Protected with SCENARIO_LOCK */
|
||
|
||
gboolean needs_playback_parsing;
|
||
|
||
GList *sinks; /* List of GstValidateSinkInformation */
|
||
GList *seeks; /* List of GstValidateSeekInformation */
|
||
|
||
/* Seek currently applied (set when all sinks received segment with
|
||
* an identical seqnum and there is a matching pending seek).
|
||
* do not free, should always be present in the seek list above */
|
||
GstValidateSeekInformation *current_seek;
|
||
/* Current unified seqnum. Set when all sinks received segment with
|
||
* an identical seqnum, even if there wasn't a matching pending seek
|
||
*/
|
||
guint32 current_seqnum;
|
||
|
||
/* List of action that need parsing when reaching ASYNC_DONE
|
||
* most probably to be able to query duration */
|
||
|
||
/* seek_flags :
|
||
* * Only set for seek actions, and only if seek succeeded
|
||
* * Only Used in _check_position()
|
||
* FIXME : Just use the seek information */
|
||
GstSeekFlags seek_flags;
|
||
GstFormat seek_format;
|
||
|
||
/* segment_start/segment_stop :
|
||
* * Set : from seek values
|
||
* * Read : In _check_position()
|
||
* FIXME : Just use the current seek information */
|
||
GstClockTime segment_start;
|
||
GstClockTime segment_stop;
|
||
|
||
/* Always initialized to a default value
|
||
* FIXME : Is it still needed with the new seeking validation system ? */
|
||
GstClockTime seek_pos_tol;
|
||
|
||
/* If we seeked in paused the position should be exactly what
|
||
* the seek value was (if accurate) */
|
||
gboolean seeked_in_pause;
|
||
|
||
guint num_actions;
|
||
|
||
gboolean handles_state;
|
||
|
||
guint execute_actions_source_id; /* MT safe. Protect with SCENARIO_LOCK */
|
||
guint wait_id;
|
||
guint signal_handler_id; /* MT safe. Protect with SCENARIO_LOCK */
|
||
guint action_execution_interval;
|
||
|
||
/* Name of message the wait action is waiting for */
|
||
GstValidateAction *wait_message_action;
|
||
|
||
gboolean buffering;
|
||
|
||
gboolean got_eos;
|
||
gboolean changing_state;
|
||
gboolean needs_async_done;
|
||
gboolean ignore_eos;
|
||
gboolean ignore_invalid_positions;
|
||
gboolean allow_errors;
|
||
GstState target_state;
|
||
|
||
GList *overrides;
|
||
|
||
gchar *pipeline_name;
|
||
GstClockTime max_latency;
|
||
gint dropped;
|
||
gint max_dropped;
|
||
|
||
/* 'switch-track action' currently waiting for
|
||
* GST_MESSAGE_STREAMS_SELECTED to be completed. */
|
||
GstValidateAction *pending_switch_track;
|
||
|
||
GstStructure *vars;
|
||
|
||
GWeakRef ref_pipeline;
|
||
|
||
GstTestClock *clock;
|
||
guint segments_needed;
|
||
|
||
GMainContext *context;
|
||
|
||
GArray /*< HTTPServer> */ * http_servers;
|
||
};
|
||
|
||
typedef struct KeyFileGroupName
|
||
{
|
||
GKeyFile *kf;
|
||
gchar *group_name;
|
||
} KeyFileGroupName;
|
||
|
||
#define NOT_KF_AFTER_FORCE_KF_EVT_TOLERANCE 1
|
||
|
||
static void
|
||
gst_validate_sink_information_free (GstValidateSinkInformation * info)
|
||
{
|
||
gst_object_unref (info->sink);
|
||
g_free (info);
|
||
}
|
||
|
||
static void
|
||
gst_validate_seek_information_free (GstValidateSeekInformation * info)
|
||
{
|
||
gst_validate_action_unref (info->action);
|
||
g_free (info);
|
||
}
|
||
|
||
static GstValidateInterceptionReturn
|
||
gst_validate_scenario_intercept_report (GstValidateReporter * reporter,
|
||
GstValidateReport * report)
|
||
{
|
||
GList *tmp;
|
||
|
||
for (tmp = GST_VALIDATE_SCENARIO (reporter)->priv->overrides; tmp;
|
||
tmp = tmp->next) {
|
||
GstValidateOverride *override = (GstValidateOverride *) tmp->data;
|
||
report->level =
|
||
gst_validate_override_get_severity (override,
|
||
gst_validate_issue_get_id (report->issue), report->level);
|
||
}
|
||
|
||
return GST_VALIDATE_REPORTER_REPORT;
|
||
}
|
||
|
||
/**
|
||
* gst_validate_scenario_get_pipeline:
|
||
* @scenario: The scenario to retrieve a pipeline from
|
||
*
|
||
* Returns: (transfer full) (nullable): The #GstPipeline the scenario is running
|
||
* against
|
||
*/
|
||
GstElement *
|
||
gst_validate_scenario_get_pipeline (GstValidateScenario * scenario)
|
||
{
|
||
return g_weak_ref_get (&scenario->priv->ref_pipeline);
|
||
}
|
||
|
||
static GstPipeline *
|
||
_get_pipeline (GstValidateReporter * reporter)
|
||
{
|
||
return
|
||
GST_PIPELINE_CAST (gst_validate_scenario_get_pipeline
|
||
(GST_VALIDATE_SCENARIO (reporter)));
|
||
}
|
||
|
||
static void
|
||
_reporter_iface_init (GstValidateReporterInterface * iface)
|
||
{
|
||
iface->intercept_report = gst_validate_scenario_intercept_report;
|
||
iface->get_pipeline = _get_pipeline;
|
||
}
|
||
|
||
static GQuark chain_qdata;
|
||
G_DEFINE_TYPE_WITH_CODE (GstValidateScenario, gst_validate_scenario,
|
||
GST_TYPE_OBJECT, G_ADD_PRIVATE (GstValidateScenario)
|
||
G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, _reporter_iface_init)
|
||
chain_qdata = g_quark_from_static_string ("__validate_scenario_chain_data")
|
||
);
|
||
|
||
/* GstValidateAction implementation */
|
||
static GType _gst_validate_action_type = 0;
|
||
|
||
struct _GstValidateActionPrivate
|
||
{
|
||
GstStructure *main_structure;
|
||
GstValidateExecuteActionReturn state; /* Actually ActionState */
|
||
gboolean printed;
|
||
gboolean executing_last_subaction;
|
||
gboolean subaction_level;
|
||
gboolean optional;
|
||
|
||
GstClockTime execution_time;
|
||
GstClockTime execution_duration;
|
||
GstClockTime timeout;
|
||
|
||
GWeakRef scenario;
|
||
gboolean needs_playback_parsing;
|
||
gboolean pending_set_done;
|
||
|
||
GMainContext *context;
|
||
|
||
GValue it_value;
|
||
GWeakRef sub_pipeline;
|
||
};
|
||
|
||
static JsonNode *
|
||
gst_validate_action_serialize (GstValidateAction * action)
|
||
{
|
||
JsonNode *node = json_node_alloc ();
|
||
JsonObject *jreport = json_object_new ();
|
||
gchar *action_args = gst_structure_to_string (action->structure);
|
||
|
||
json_object_set_string_member (jreport, "type", "action");
|
||
json_object_set_string_member (jreport, "action-type", action->type);
|
||
json_object_set_int_member (jreport, "playback-time",
|
||
(gint64) action->playback_time);
|
||
json_object_set_string_member (jreport, "args", action_args);
|
||
g_free (action_args);
|
||
|
||
node = json_node_init_object (node, jreport);
|
||
json_object_unref (jreport);
|
||
|
||
return node;
|
||
}
|
||
|
||
GType
|
||
gst_validate_action_get_type (void)
|
||
{
|
||
if (_gst_validate_action_type == 0) {
|
||
_gst_validate_action_type =
|
||
g_boxed_type_register_static (g_intern_static_string
|
||
("GstValidateAction"), (GBoxedCopyFunc) gst_validate_action_ref,
|
||
(GBoxedFreeFunc) gst_validate_action_unref);
|
||
|
||
json_boxed_register_serialize_func (_gst_validate_action_type,
|
||
JSON_NODE_OBJECT,
|
||
(JsonBoxedSerializeFunc) gst_validate_action_serialize);
|
||
}
|
||
|
||
return _gst_validate_action_type;
|
||
}
|
||
|
||
static gboolean execute_next_action (GstValidateScenario * scenario);
|
||
static gboolean
|
||
gst_validate_scenario_load (GstValidateScenario * scenario,
|
||
const gchar * scenario_name);
|
||
|
||
static GstValidateAction *
|
||
_action_copy (GstValidateAction * act)
|
||
{
|
||
GstValidateScenario *scenario = gst_validate_action_get_scenario (act);
|
||
GstValidateAction *copy = gst_validate_action_new (scenario,
|
||
_find_action_type (act->type), NULL, FALSE);
|
||
|
||
gst_object_unref (scenario);
|
||
|
||
if (act->structure) {
|
||
copy->structure = gst_structure_copy (act->structure);
|
||
copy->type = gst_structure_get_name (copy->structure);
|
||
if (!(copy->name = gst_structure_get_string (copy->structure, "name")))
|
||
copy->name = "";
|
||
}
|
||
|
||
if (act->priv->main_structure)
|
||
copy->priv->main_structure = gst_structure_copy (act->priv->main_structure);
|
||
|
||
copy->action_number = act->action_number;
|
||
copy->playback_time = act->playback_time;
|
||
copy->priv->timeout = act->priv->timeout;
|
||
GST_VALIDATE_ACTION_LINENO (copy) = GST_VALIDATE_ACTION_LINENO (act);
|
||
GST_VALIDATE_ACTION_FILENAME (copy) =
|
||
g_strdup (GST_VALIDATE_ACTION_FILENAME (act));
|
||
GST_VALIDATE_ACTION_DEBUG (copy) = g_strdup (GST_VALIDATE_ACTION_DEBUG (act));
|
||
GST_VALIDATE_ACTION_N_REPEATS (copy) = GST_VALIDATE_ACTION_N_REPEATS (act);
|
||
GST_VALIDATE_ACTION_RANGE_NAME (copy) =
|
||
g_strdup (GST_VALIDATE_ACTION_RANGE_NAME (act));
|
||
|
||
if (act->priv->it_value.g_type != 0) {
|
||
g_value_init (©->priv->it_value, G_VALUE_TYPE (&act->priv->it_value));
|
||
g_value_copy (&act->priv->it_value, ©->priv->it_value);
|
||
}
|
||
|
||
return copy;
|
||
}
|
||
|
||
const gchar *
|
||
gst_validate_action_return_get_name (GstValidateActionReturn r)
|
||
{
|
||
switch (r) {
|
||
case GST_VALIDATE_EXECUTE_ACTION_ERROR:
|
||
return "ERROR";
|
||
case GST_VALIDATE_EXECUTE_ACTION_OK:
|
||
return "OK";
|
||
case GST_VALIDATE_EXECUTE_ACTION_ASYNC:
|
||
return "ASYNC";
|
||
case GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING:
|
||
return "NON-BLOCKING";
|
||
case GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED:
|
||
return "ERROR(reported)";
|
||
case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS:
|
||
return "IN_PROGRESS";
|
||
case GST_VALIDATE_EXECUTE_ACTION_NONE:
|
||
return "NONE";
|
||
case GST_VALIDATE_EXECUTE_ACTION_DONE:
|
||
return "DONE";
|
||
}
|
||
g_assert_not_reached ();
|
||
return "???";
|
||
}
|
||
|
||
static void
|
||
_action_free (GstValidateAction * action)
|
||
{
|
||
if (action->structure)
|
||
gst_structure_free (action->structure);
|
||
|
||
if (action->priv->main_structure)
|
||
gst_structure_free (action->priv->main_structure);
|
||
|
||
if (action->priv->it_value.g_type != 0)
|
||
g_value_reset (&action->priv->it_value);
|
||
g_weak_ref_clear (&action->priv->scenario);
|
||
g_weak_ref_clear (&action->priv->sub_pipeline);
|
||
g_free (GST_VALIDATE_ACTION_FILENAME (action));
|
||
g_free (GST_VALIDATE_ACTION_DEBUG (action));
|
||
|
||
g_free (action->rangename);
|
||
g_free (action->priv);
|
||
g_free (action);
|
||
}
|
||
|
||
static void
|
||
gst_validate_action_init (GstValidateAction * action)
|
||
{
|
||
gst_mini_object_init (((GstMiniObject *) action), 0,
|
||
_gst_validate_action_type, (GstMiniObjectCopyFunction) _action_copy, NULL,
|
||
(GstMiniObjectFreeFunction) _action_free);
|
||
|
||
action->priv = g_new0 (GstValidateActionPrivate, 1);
|
||
|
||
g_weak_ref_init (&action->priv->scenario, NULL);
|
||
}
|
||
|
||
GstValidateAction *
|
||
gst_validate_action_ref (GstValidateAction * action)
|
||
{
|
||
return (GstValidateAction *) gst_mini_object_ref (GST_MINI_OBJECT (action));
|
||
}
|
||
|
||
void
|
||
gst_validate_action_unref (GstValidateAction * action)
|
||
{
|
||
gst_mini_object_unref (GST_MINI_OBJECT (action));
|
||
}
|
||
|
||
/**
|
||
* gst_validate_action_new:
|
||
* @scenario: (allow-none): The scenario executing the action
|
||
* @action_type: The action type
|
||
* @structure: The structure containing the action arguments
|
||
* @add_to_lists: Weather the action should be added to the scenario action list
|
||
*
|
||
* Returns: A newly created #GstValidateAction
|
||
*/
|
||
GstValidateAction *
|
||
gst_validate_action_new (GstValidateScenario * scenario,
|
||
GstValidateActionType * action_type, GstStructure * structure,
|
||
gboolean add_to_lists)
|
||
{
|
||
GstValidateAction *action = g_new0 (GstValidateAction, 1);
|
||
|
||
g_assert (action_type);
|
||
|
||
gst_validate_action_init (action);
|
||
action->playback_time = GST_CLOCK_TIME_NONE;
|
||
action->priv->timeout = GST_CLOCK_TIME_NONE;
|
||
action->priv->state = GST_VALIDATE_EXECUTE_ACTION_NONE;
|
||
action->type = action_type->name;
|
||
action->repeat = -1;
|
||
|
||
g_weak_ref_set (&action->priv->scenario, scenario);
|
||
if (structure) {
|
||
gchar *filename = NULL;
|
||
gst_structure_get (structure,
|
||
"__lineno__", G_TYPE_INT, &GST_VALIDATE_ACTION_LINENO (action),
|
||
"__filename__", G_TYPE_STRING, &filename,
|
||
"__debug__", G_TYPE_STRING, &GST_VALIDATE_ACTION_DEBUG (action), NULL);
|
||
if (filename) {
|
||
GST_VALIDATE_ACTION_FILENAME (action) =
|
||
g_filename_display_basename (filename);
|
||
g_free (filename);
|
||
}
|
||
gst_structure_remove_fields (structure, "__lineno__", "__filename__",
|
||
"__debug__", NULL);
|
||
action->priv->state =
|
||
_fill_action (scenario, action, structure, add_to_lists);
|
||
}
|
||
|
||
return action;
|
||
}
|
||
|
||
gboolean
|
||
_action_check_and_set_printed (GstValidateAction * action)
|
||
{
|
||
if (action->priv->printed == FALSE) {
|
||
gst_validate_send (json_boxed_serialize (GST_MINI_OBJECT_TYPE
|
||
(action), action));
|
||
|
||
action->priv->printed = TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/**
|
||
* gst_validate_action_get_sub_pipeline:
|
||
* @action: The action to retrieve sub pipeline from
|
||
*
|
||
* Returns: (transfer full): The sub pipeline of @action if the action is running a sub pipeline
|
||
*/
|
||
|
||
static GstPipeline *
|
||
gst_validate_action_get_sub_pipeline (GstValidateAction * action)
|
||
{
|
||
return g_weak_ref_get (&action->priv->sub_pipeline);
|
||
}
|
||
|
||
static GstElement *
|
||
gst_validate_scenario_get_sub_pipeline (GstValidateScenario * scenario,
|
||
const gchar * pipeline_name)
|
||
{
|
||
GstPipeline *pipeline = NULL;
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
for (GList * tmp = scenario->priv->non_blocking_running_actions; tmp;
|
||
tmp = tmp->next) {
|
||
pipeline = gst_validate_action_get_sub_pipeline (tmp->data);
|
||
if (pipeline && !g_strcmp0 (GST_OBJECT_NAME (pipeline), pipeline_name)) {
|
||
break;
|
||
}
|
||
gst_clear_object (&pipeline);
|
||
}
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
return (GstElement *) pipeline;
|
||
}
|
||
|
||
gint
|
||
gst_validate_action_get_level (GstValidateAction * action)
|
||
{
|
||
return action->priv->subaction_level;
|
||
}
|
||
|
||
/* GstValidateActionType implementation */
|
||
GType _gst_validate_action_type_type;
|
||
GST_DEFINE_MINI_OBJECT_TYPE (GstValidateActionType, gst_validate_action_type);
|
||
static GstValidateActionType *gst_validate_action_type_new (void);
|
||
|
||
struct _GstValidateActionTypePrivate
|
||
{
|
||
gint n_calls;
|
||
};
|
||
|
||
static void
|
||
_action_type_free (GstValidateActionType * type)
|
||
{
|
||
for (gint i = 0; type->parameters[i].name; i++) {
|
||
if (type->parameters[i].free) {
|
||
type->parameters[i].free (&type->parameters[i]);
|
||
}
|
||
}
|
||
|
||
g_free (type->parameters);
|
||
g_free (type->description);
|
||
g_free (type->name);
|
||
g_free (type->implementer_namespace);
|
||
g_free (type->priv);
|
||
|
||
if (type->overriden_type)
|
||
gst_mini_object_unref (GST_MINI_OBJECT (type->overriden_type));
|
||
|
||
g_free (type);
|
||
}
|
||
|
||
static void
|
||
gst_validate_action_type_init (GstValidateActionType * type)
|
||
{
|
||
type->priv = g_new0 (GstValidateActionTypePrivate, 1);
|
||
|
||
gst_mini_object_init ((GstMiniObject *) type, 0,
|
||
_gst_validate_action_type_type, NULL, NULL,
|
||
(GstMiniObjectFreeFunction) _action_type_free);
|
||
}
|
||
|
||
GstValidateActionType *
|
||
gst_validate_action_type_new (void)
|
||
{
|
||
GstValidateActionType *type = g_new0 (GstValidateActionType, 1);
|
||
|
||
gst_validate_action_type_init (type);
|
||
|
||
return type;
|
||
}
|
||
|
||
static GstValidateActionType *
|
||
_find_action_type (const gchar * type_name)
|
||
{
|
||
GList *tmp;
|
||
|
||
for (tmp = action_types; tmp; tmp = tmp->next) {
|
||
GstValidateActionType *atype = (GstValidateActionType *) tmp->data;
|
||
if (g_strcmp0 (atype->name, type_name) == 0)
|
||
return atype;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
_update_well_known_vars (GstValidateScenario * scenario)
|
||
{
|
||
gint64 duration, position;
|
||
gdouble dduration, dposition;
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
|
||
gst_structure_remove_fields (scenario->priv->vars, "position", "duration",
|
||
NULL);
|
||
|
||
if (!pipeline)
|
||
return;
|
||
|
||
if (!gst_element_query_duration (pipeline, GST_FORMAT_TIME, &duration) ||
|
||
!GST_CLOCK_TIME_IS_VALID (duration)) {
|
||
GstValidateMonitor *monitor =
|
||
(GstValidateMonitor *) (g_object_get_data ((GObject *)
|
||
pipeline, "validate-monitor"));
|
||
GST_INFO_OBJECT (scenario,
|
||
"Could not query duration. Trying to get duration from media-info");
|
||
if (monitor && monitor->media_descriptor)
|
||
duration =
|
||
gst_validate_media_descriptor_get_duration
|
||
(monitor->media_descriptor);
|
||
}
|
||
|
||
if (!GST_CLOCK_TIME_IS_VALID (duration))
|
||
dduration = G_MAXDOUBLE;
|
||
else
|
||
dduration = ((double) duration / GST_SECOND);
|
||
|
||
gst_structure_set (scenario->priv->vars, "duration", G_TYPE_DOUBLE, dduration,
|
||
NULL);
|
||
if (gst_element_query_position (pipeline, GST_FORMAT_TIME, &position)) {
|
||
|
||
if (!GST_CLOCK_TIME_IS_VALID (position))
|
||
dposition = G_MAXDOUBLE;
|
||
else
|
||
dposition = ((double) position / GST_SECOND);
|
||
|
||
gst_structure_set (scenario->priv->vars, "position", G_TYPE_DOUBLE,
|
||
dposition, NULL);
|
||
} else {
|
||
GST_INFO_OBJECT (scenario, "Could not query position");
|
||
}
|
||
}
|
||
|
||
static GstElement *_get_target_element (GstValidateScenario * scenario,
|
||
GstValidateAction * action);
|
||
|
||
static GstObject *
|
||
_get_target_object_property (GstValidateScenario * scenario,
|
||
GstValidateAction * action, const gchar * property_path,
|
||
GParamSpec ** pspec)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
gchar **elem_pad_name = NULL;
|
||
gchar **object_prop_name = NULL;
|
||
const gchar *elemname;
|
||
const gchar *padname = NULL;
|
||
GstObject *target = NULL;
|
||
gint i;
|
||
|
||
elem_pad_name = g_strsplit (property_path, ".", 2);
|
||
object_prop_name =
|
||
g_strsplit (elem_pad_name[1] ? elem_pad_name[1] : elem_pad_name[0], "::",
|
||
-1);
|
||
REPORT_UNLESS (object_prop_name[1], err,
|
||
"Property specification %s is missing a `::propename` part",
|
||
property_path);
|
||
|
||
if (elem_pad_name[1]) {
|
||
elemname = elem_pad_name[0];
|
||
padname = object_prop_name[0];
|
||
} else {
|
||
elemname = object_prop_name[0];
|
||
}
|
||
|
||
gst_structure_set (action->structure, "target-element-name", G_TYPE_STRING,
|
||
elemname, NULL);
|
||
|
||
target = (GstObject *) _get_target_element (scenario, action);
|
||
gst_structure_remove_field (action->structure, "target-element-name");
|
||
REPORT_UNLESS (target, err, "Target element with given name (%s) not found",
|
||
elemname);
|
||
|
||
if (padname) {
|
||
gboolean done = FALSE;
|
||
GstIterator *it = gst_element_iterate_pads (GST_ELEMENT (target));
|
||
GValue v = G_VALUE_INIT;
|
||
|
||
gst_clear_object (&target);
|
||
while (!done) {
|
||
switch (gst_iterator_next (it, &v)) {
|
||
case GST_ITERATOR_OK:{
|
||
GstPad *pad = g_value_get_object (&v);
|
||
gchar *name = gst_object_get_name (GST_OBJECT (pad));
|
||
|
||
if (!g_strcmp0 (name, padname)) {
|
||
done = TRUE;
|
||
gst_clear_object (&target);
|
||
|
||
target = gst_object_ref (pad);
|
||
}
|
||
g_free (name);
|
||
g_value_reset (&v);
|
||
break;
|
||
}
|
||
case GST_ITERATOR_RESYNC:
|
||
gst_iterator_resync (it);
|
||
break;
|
||
case GST_ITERATOR_ERROR:
|
||
case GST_ITERATOR_DONE:
|
||
done = TRUE;
|
||
}
|
||
}
|
||
|
||
gst_iterator_free (it);
|
||
}
|
||
REPORT_UNLESS (target, err, "Could not find pad: %s::%s", elemname, padname);
|
||
|
||
for (i = 1;;) {
|
||
const gchar *propname = object_prop_name[i];
|
||
|
||
*pspec =
|
||
g_object_class_find_property (G_OBJECT_GET_CLASS (target), propname);
|
||
|
||
REPORT_UNLESS (*pspec, err,
|
||
"Object %" GST_PTR_FORMAT " doesn't have a property call '%s'", target,
|
||
propname);
|
||
|
||
if (!object_prop_name[++i])
|
||
break;
|
||
|
||
REPORT_UNLESS (g_type_is_a ((*pspec)->owner_type, G_TYPE_OBJECT), err,
|
||
"Property: %" GST_PTR_FORMAT "::%s not a GObject, can't use it.",
|
||
target, propname);
|
||
|
||
g_object_get (target, propname, &target, NULL);
|
||
REPORT_UNLESS (target, err,
|
||
"Property: %" GST_PTR_FORMAT "::%s is NULL can't get %s.",
|
||
target, propname, object_prop_name[i + 1]);
|
||
}
|
||
|
||
REPORT_UNLESS (res == GST_VALIDATE_EXECUTE_ACTION_OK, err, "Something fishy");
|
||
|
||
done:
|
||
g_strfreev (elem_pad_name);
|
||
g_strfreev (object_prop_name);
|
||
return target;
|
||
|
||
err:
|
||
gst_clear_object (&target);
|
||
goto done;
|
||
}
|
||
|
||
static gboolean
|
||
_set_variable_func (const gchar * name, double *value, gpointer user_data)
|
||
{
|
||
GstValidateScenario *scenario = (GstValidateScenario *) user_data;
|
||
|
||
if (!gst_structure_get_double (scenario->priv->vars, name, value))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Check that @list doesn't contain any non-optional actions */
|
||
static gboolean
|
||
actions_list_is_done (GList * list)
|
||
{
|
||
GList *l;
|
||
|
||
for (l = list; l != NULL; l = g_list_next (l)) {
|
||
GstValidateAction *action = l->data;
|
||
|
||
if (!action->priv->optional)
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
_check_scenario_is_done (GstValidateScenario * scenario)
|
||
{
|
||
SCENARIO_LOCK (scenario);
|
||
if (actions_list_is_done (scenario->priv->actions) &&
|
||
actions_list_is_done (scenario->priv->non_blocking_running_actions) &&
|
||
actions_list_is_done (scenario->priv->on_addition_actions)) {
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
g_signal_emit (scenario, scenario_signals[DONE], 0);
|
||
} else {
|
||
SCENARIO_UNLOCK (scenario);
|
||
}
|
||
}
|
||
|
||
static void
|
||
_reset_sink_information (GstValidateSinkInformation * sinkinfo)
|
||
{
|
||
sinkinfo->segment_seqnum = GST_SEQNUM_INVALID;
|
||
gst_segment_init (&sinkinfo->segment, GST_FORMAT_UNDEFINED);
|
||
}
|
||
|
||
/**
|
||
* gst_validate_action_get_clocktime:
|
||
* @scenario: The #GstValidateScenario from which to get a time
|
||
* for a parameter of an action
|
||
* @action: The action from which to retrieve the time for @name
|
||
* parameter.
|
||
* @name: The name of the parameter for which to retrieve a time
|
||
* @retval: (out): The return value for the wanted time
|
||
*
|
||
* Get a time value for the @name parameter of an action. This
|
||
* method should be called to retrieve and compute a timed value of a given
|
||
* action. It will first try to retrieve the value as a double,
|
||
* then get it as a string and execute any formula taking into account
|
||
* the 'position' and 'duration' variables. And it will always convert that
|
||
* value to a GstClockTime.
|
||
*
|
||
* Returns: %TRUE if the time value could be retrieved/computed or %FALSE otherwise
|
||
*/
|
||
gboolean
|
||
gst_validate_action_get_clocktime (GstValidateScenario * scenario,
|
||
GstValidateAction * action, const gchar * name, GstClockTime * retval)
|
||
{
|
||
|
||
if (!gst_structure_has_field (action->structure, name))
|
||
return FALSE;
|
||
|
||
if (!gst_validate_utils_get_clocktime (action->structure, name, retval)) {
|
||
gdouble val;
|
||
gchar *error = NULL, *strval;
|
||
const gchar *tmpvalue = gst_structure_get_string (action->structure, name);
|
||
|
||
if (!tmpvalue) {
|
||
GST_INFO_OBJECT (scenario, "Could not find %s (%" GST_PTR_FORMAT ")",
|
||
name, action->structure);
|
||
return -1;
|
||
}
|
||
|
||
_update_well_known_vars (scenario);
|
||
strval =
|
||
gst_validate_replace_variables_in_string (action, scenario->priv->vars,
|
||
tmpvalue, GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_ALL);
|
||
if (!strval)
|
||
return FALSE;
|
||
|
||
val =
|
||
gst_validate_utils_parse_expression (strval, _set_variable_func,
|
||
scenario, &error);
|
||
if (error) {
|
||
GST_WARNING ("Error while parsing %s: %s (%" GST_PTR_FORMAT ")",
|
||
strval, error, scenario->priv->vars);
|
||
g_free (error);
|
||
g_free (strval);
|
||
|
||
return FALSE;
|
||
} else if (val == -1.0) {
|
||
*retval = GST_CLOCK_TIME_NONE;
|
||
} else {
|
||
gint n, d;
|
||
|
||
gst_util_double_to_fraction (val, &n, &d);
|
||
*retval = gst_util_uint64_scale_int_round (n, GST_SECOND, d);
|
||
}
|
||
gst_structure_set (action->structure, name, G_TYPE_UINT64, *retval, NULL);
|
||
g_free (strval);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* WITH SCENARIO LOCK TAKEN */
|
||
static GstValidateSinkInformation *
|
||
_find_sink_information (GstValidateScenario * scenario, GstElement * sink)
|
||
{
|
||
GList *tmp;
|
||
|
||
for (tmp = scenario->priv->sinks; tmp; tmp = tmp->next) {
|
||
GstValidateSinkInformation *sink_info =
|
||
(GstValidateSinkInformation *) tmp->data;
|
||
if (sink_info->sink == sink)
|
||
return sink_info;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* WITH SCENARIO LOCK TAKEN */
|
||
static GstValidateSeekInformation *
|
||
_find_seek_information (GstValidateScenario * scenario, guint32 seqnum)
|
||
{
|
||
GList *tmp;
|
||
|
||
for (tmp = scenario->priv->seeks; tmp; tmp = tmp->next) {
|
||
GstValidateSeekInformation *seek_info =
|
||
(GstValidateSeekInformation *) tmp->data;
|
||
if (seek_info->seqnum == seqnum)
|
||
return seek_info;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* WITH SCENARIO LOCK TAKEN */
|
||
static void
|
||
_validate_sink_information (GstValidateScenario * scenario)
|
||
{
|
||
GList *tmp;
|
||
gboolean all_sinks_ready = TRUE;
|
||
gboolean identical_seqnum = TRUE;
|
||
gboolean transitioning = FALSE;
|
||
guint32 common_seqnum = GST_SEQNUM_INVALID;
|
||
guint32 next_seqnum = GST_SEQNUM_INVALID;
|
||
GstValidateSeekInformation *seek_info;
|
||
|
||
if (scenario->priv->seeks)
|
||
/* If we have a pending seek, get the expected seqnum to
|
||
* figure out whether we are transitioning to a seek */
|
||
next_seqnum =
|
||
((GstValidateSeekInformation *) scenario->priv->seeks->data)->seqnum;
|
||
|
||
GST_LOG_OBJECT (scenario, "next_seqnum %" G_GUINT32_FORMAT, next_seqnum);
|
||
|
||
for (tmp = scenario->priv->sinks; tmp; tmp = tmp->next) {
|
||
GstValidateSinkInformation *sink_info =
|
||
(GstValidateSinkInformation *) tmp->data;
|
||
GST_DEBUG_OBJECT (sink_info->sink,
|
||
"seqnum:%" G_GUINT32_FORMAT " segment:%" GST_SEGMENT_FORMAT,
|
||
sink_info->segment_seqnum, &sink_info->segment);
|
||
if (sink_info->segment_seqnum == GST_SEQNUM_INVALID)
|
||
all_sinks_ready = FALSE;
|
||
else if (sink_info->segment.format == GST_FORMAT_TIME) {
|
||
/* Are we in the middle of switching segments (from the current
|
||
* one, or to the next week) ? */
|
||
if (sink_info->segment_seqnum == scenario->priv->current_seqnum ||
|
||
sink_info->segment_seqnum == next_seqnum)
|
||
transitioning = TRUE;
|
||
|
||
/* We are only interested in sinks that handle TIME segments */
|
||
if (common_seqnum == GST_SEQNUM_INVALID)
|
||
common_seqnum = sink_info->segment_seqnum;
|
||
else if (common_seqnum != sink_info->segment_seqnum) {
|
||
identical_seqnum = FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* If not all sinks have received a segment, just return */
|
||
if (!all_sinks_ready)
|
||
return;
|
||
|
||
GST_FIXME_OBJECT (scenario,
|
||
"All sinks have valid segment. identical_seqnum:%d transitioning:%d seqnum:%"
|
||
G_GUINT32_FORMAT " (current:%" G_GUINT32_FORMAT ") seeks:%p",
|
||
identical_seqnum, transitioning, common_seqnum,
|
||
scenario->priv->current_seqnum, scenario->priv->seeks);
|
||
|
||
if (!identical_seqnum) {
|
||
/* If all sinks received a segment *and* there is a pending seek *and* there
|
||
* wasn't one previously, we definitely have a failure */
|
||
if (!transitioning && scenario->priv->current_seek == NULL
|
||
&& scenario->priv->seeks) {
|
||
GST_VALIDATE_REPORT (scenario, EVENT_SEEK_INVALID_SEQNUM,
|
||
"Not all segments from a given seek have the same seqnum");
|
||
return;
|
||
}
|
||
/* Otherwise we're either doing the initial preroll (without seek)
|
||
* or we are in the middle of switching to another seek */
|
||
return;
|
||
}
|
||
|
||
/* Now check if we have seek data related to that seqnum */
|
||
seek_info = _find_seek_information (scenario, common_seqnum);
|
||
|
||
if (seek_info && seek_info != scenario->priv->current_seek) {
|
||
GST_DEBUG_OBJECT (scenario, "Found a corresponding seek !");
|
||
/* Updating values */
|
||
/* FIXME : Check segment values if needed ! */
|
||
/* FIXME : Non-flushing seek, validate here */
|
||
if (seek_info->start_type == GST_SEEK_TYPE_SET)
|
||
scenario->priv->segment_start = seek_info->start;
|
||
if (seek_info->stop_type == GST_SEEK_TYPE_SET)
|
||
scenario->priv->segment_stop = seek_info->stop;
|
||
if (scenario->priv->target_state == GST_STATE_PAUSED)
|
||
scenario->priv->seeked_in_pause = TRUE;
|
||
SCENARIO_UNLOCK (scenario);
|
||
/* If it's a non-flushing seek, validate it here
|
||
* otherwise we will do it when the async_done is received */
|
||
if (!(seek_info->flags & GST_SEEK_FLAG_FLUSH))
|
||
gst_validate_action_set_done (seek_info->action);
|
||
SCENARIO_LOCK (scenario);
|
||
}
|
||
/* We always set the current_seek. Can be NULL if no matching */
|
||
scenario->priv->current_seek = seek_info;
|
||
scenario->priv->current_seqnum = common_seqnum;
|
||
}
|
||
|
||
/**
|
||
* gst_validate_scenario_execute_seek:
|
||
* @scenario: The #GstValidateScenario for which to execute a seek action
|
||
* @action: The seek action to execute
|
||
* @rate: The playback rate of the seek
|
||
* @format: The #GstFormat of the seek
|
||
* @flags: The #GstSeekFlags of the seek
|
||
* @start_type: The #GstSeekType of the start value of the seek
|
||
* @start: The start time of the seek
|
||
* @stop_type: The #GstSeekType of the stop value of the seek
|
||
* @stop: The stop time of the seek
|
||
*
|
||
* Executes a seek event on the scenario's pipeline. You should always use
|
||
* this method when you want to execute a seek inside a new action type
|
||
* so that the scenario state is updated taking into account that seek.
|
||
*
|
||
* For more information you should have a look at #gst_event_new_seek
|
||
*
|
||
* Returns: %TRUE if the seek could be executed, %FALSE otherwise
|
||
*/
|
||
GstValidateExecuteActionReturn
|
||
gst_validate_scenario_execute_seek (GstValidateScenario * scenario,
|
||
GstValidateAction * action, gdouble rate, GstFormat format,
|
||
GstSeekFlags flags, GstSeekType start_type, GstClockTime start,
|
||
GstSeekType stop_type, GstClockTime stop)
|
||
{
|
||
GstEvent *seek;
|
||
GstValidateSeekInformation *seek_info;
|
||
|
||
GstValidateExecuteActionReturn ret = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
seek = gst_event_new_seek (rate, format, flags, start_type, start,
|
||
stop_type, stop);
|
||
|
||
if (format != GST_FORMAT_TIME && format != GST_FORMAT_DEFAULT) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Trying to seek in format %d, but not support yet!", format);
|
||
}
|
||
|
||
seek_info = g_new0 (GstValidateSeekInformation, 1);
|
||
seek_info->seqnum = GST_EVENT_SEQNUM (seek);
|
||
seek_info->rate = rate;
|
||
seek_info->format = format;
|
||
seek_info->flags = flags;
|
||
seek_info->start = start;
|
||
seek_info->stop = stop;
|
||
seek_info->start_type = start_type;
|
||
seek_info->stop_type = stop_type;
|
||
seek_info->action = gst_validate_action_ref (action);
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
priv->seeks = g_list_append (priv->seeks, seek_info);
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
gst_event_ref (seek);
|
||
if (gst_element_send_event (pipeline, seek)) {
|
||
priv->seek_flags = flags;
|
||
priv->seek_format = format;
|
||
} else {
|
||
switch (format) {
|
||
case GST_FORMAT_TIME:
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action, EVENT_SEEK_NOT_HANDLED,
|
||
"Could not execute seek: '(position %" GST_TIME_FORMAT
|
||
"), %s (num %u, missing repeat: %i), seeking to: %" GST_TIME_FORMAT
|
||
" stop: %" GST_TIME_FORMAT " Rate %lf' on %" GST_PTR_FORMAT,
|
||
GST_TIME_ARGS (action->playback_time), action->name,
|
||
action->action_number, action->repeat, GST_TIME_ARGS (start),
|
||
GST_TIME_ARGS (stop), rate, pipeline);
|
||
break;
|
||
default:
|
||
{
|
||
gchar *format_str = g_enum_to_string (GST_TYPE_FORMAT, format);
|
||
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action, EVENT_SEEK_NOT_HANDLED,
|
||
"Could not execute seek in format %s '(position %" GST_TIME_FORMAT
|
||
"), %s (num %u, missing repeat: %i), seeking to: %" G_GINT64_FORMAT
|
||
" stop: %" G_GINT64_FORMAT " Rate %lf'", format_str,
|
||
GST_TIME_ARGS (action->playback_time), action->name,
|
||
action->action_number, action->repeat, start, stop, rate);
|
||
g_free (format_str);
|
||
break;
|
||
}
|
||
}
|
||
SCENARIO_LOCK (scenario);
|
||
priv->seeks = g_list_remove (priv->seeks, seek_info);
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
gst_validate_seek_information_free (seek_info);
|
||
ret = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
gst_event_unref (seek);
|
||
gst_object_unref (pipeline);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static gint
|
||
_execute_seek (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
const char *str_format, *str_flags, *str_start_type, *str_stop_type;
|
||
|
||
gdouble rate = 1.0;
|
||
guint format = GST_FORMAT_TIME;
|
||
GstSeekFlags flags = 0;
|
||
guint start_type = GST_SEEK_TYPE_SET;
|
||
GstClockTime start;
|
||
guint stop_type = GST_SEEK_TYPE_SET;
|
||
GstClockTime stop = GST_CLOCK_TIME_NONE;
|
||
|
||
if (!gst_validate_action_get_clocktime (scenario, action, "start", &start))
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
|
||
gst_structure_get_double (action->structure, "rate", &rate);
|
||
if ((str_format = gst_structure_get_string (action->structure, "format")))
|
||
gst_validate_utils_enum_from_str (GST_TYPE_FORMAT, str_format, &format);
|
||
|
||
if ((str_start_type =
|
||
gst_structure_get_string (action->structure, "start_type")))
|
||
gst_validate_utils_enum_from_str (GST_TYPE_SEEK_TYPE, str_start_type,
|
||
&start_type);
|
||
|
||
if ((str_stop_type =
|
||
gst_structure_get_string (action->structure, "stop_type")))
|
||
gst_validate_utils_enum_from_str (GST_TYPE_SEEK_TYPE, str_stop_type,
|
||
&stop_type);
|
||
|
||
if ((str_flags = gst_structure_get_string (action->structure, "flags")))
|
||
flags = gst_validate_utils_flags_from_str (GST_TYPE_SEEK_FLAGS, str_flags);
|
||
|
||
gst_validate_action_get_clocktime (scenario, action, "stop", &stop);
|
||
|
||
return gst_validate_scenario_execute_seek (scenario, action, rate, format,
|
||
flags, start_type, start, stop_type, stop);
|
||
}
|
||
|
||
static gboolean
|
||
_pause_action_restore_playing (GstValidateScenario * scenario)
|
||
{
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
|
||
if (!pipeline) {
|
||
GST_ERROR_OBJECT (scenario, "No pipeline set anymore!");
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
gst_validate_printf (scenario, "Back to playing\n");
|
||
|
||
if (gst_element_set_state (pipeline, GST_STATE_PLAYING) ==
|
||
GST_STATE_CHANGE_FAILURE) {
|
||
GST_VALIDATE_REPORT (scenario, STATE_CHANGE_FAILURE,
|
||
"Failed to set state to playing");
|
||
scenario->priv->target_state = GST_STATE_PLAYING;
|
||
}
|
||
|
||
gst_object_unref (pipeline);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
_set_const_func (const GstIdStr * fieldname, const GValue * value,
|
||
GstStructure * consts)
|
||
{
|
||
gst_structure_id_str_set_value (consts, fieldname, value);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_define_vars (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
gst_structure_foreach_id_str (action->structure,
|
||
(GstStructureForeachIdStrFunc) _set_const_func, scenario->priv->vars);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_set_timed_value (const GstIdStr * fieldname, const GValue * gvalue,
|
||
GstStructure * structure)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
gdouble value;
|
||
GstClockTime timestamp;
|
||
GstTimedValueControlSource *source = NULL;
|
||
GstControlBinding *binding;
|
||
GstValidateScenario *scenario;
|
||
GstValidateAction *action;
|
||
GstObject *obj = NULL;
|
||
GParamSpec *paramspec = NULL;
|
||
const gchar *field = gst_id_str_as_str (fieldname);
|
||
const gchar *unused_fields[] =
|
||
{ "binding-type", "source-type", "interpolation-mode",
|
||
"timestamp", "__scenario__", "__action__", "__res__", "repeat",
|
||
"playback-time", NULL
|
||
};
|
||
|
||
if (g_strv_contains (unused_fields, field))
|
||
return TRUE;
|
||
|
||
gst_structure_get (structure, "__scenario__", G_TYPE_POINTER, &scenario,
|
||
"__action__", G_TYPE_POINTER, &action, NULL);
|
||
|
||
|
||
if (G_VALUE_HOLDS_DOUBLE (gvalue))
|
||
value = g_value_get_double (gvalue);
|
||
else if (G_VALUE_HOLDS_INT (gvalue))
|
||
value = (gdouble) g_value_get_int (gvalue);
|
||
else {
|
||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Invalid value type for property '%s': %s",
|
||
field, G_VALUE_TYPE_NAME (gvalue));
|
||
goto err;
|
||
}
|
||
|
||
obj = _get_target_object_property (scenario, action, field, ¶mspec);
|
||
if (!obj || !paramspec) {
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto err;
|
||
}
|
||
|
||
REPORT_UNLESS (gst_validate_action_get_clocktime (scenario, action,
|
||
"timestamp", ×tamp), err,
|
||
"Could get timestamp on %" GST_PTR_FORMAT, action->structure);
|
||
|
||
binding = gst_object_get_control_binding (obj, paramspec->name);
|
||
if (!binding) {
|
||
guint mode;
|
||
GType source_type;
|
||
const gchar *interpolation_mode =
|
||
gst_structure_get_string (action->structure, "interpolation-mode");
|
||
const gchar *source_type_name =
|
||
gst_structure_get_string (action->structure, "source-type");
|
||
|
||
if (source_type_name) {
|
||
source_type = g_type_from_name (source_type_name);
|
||
|
||
REPORT_UNLESS (g_type_is_a (source_type,
|
||
GST_TYPE_TIMED_VALUE_CONTROL_SOURCE), err,
|
||
"Source type '%s' is not supported", source_type_name);
|
||
} else {
|
||
source_type = GST_TYPE_INTERPOLATION_CONTROL_SOURCE;
|
||
}
|
||
|
||
source = g_object_new (source_type, NULL);
|
||
gst_object_ref_sink (source);
|
||
if (GST_IS_INTERPOLATION_CONTROL_SOURCE (source)) {
|
||
if (interpolation_mode)
|
||
REPORT_UNLESS (gst_validate_utils_enum_from_str
|
||
(GST_TYPE_INTERPOLATION_MODE, interpolation_mode, &mode), err,
|
||
"Could not convert interpolation-mode '%s'", interpolation_mode);
|
||
|
||
else
|
||
mode = GST_INTERPOLATION_MODE_LINEAR;
|
||
|
||
g_object_set (source, "mode", mode, NULL);
|
||
}
|
||
|
||
if (!g_strcmp0 (gst_structure_get_string (action->structure,
|
||
"binding-type"), "direct-absolute")) {
|
||
binding =
|
||
gst_direct_control_binding_new_absolute (obj, paramspec->name,
|
||
GST_CONTROL_SOURCE (source));
|
||
} else {
|
||
binding =
|
||
gst_direct_control_binding_new (obj, paramspec->name,
|
||
GST_CONTROL_SOURCE (source));
|
||
}
|
||
|
||
gst_object_add_control_binding (obj, binding);
|
||
} else {
|
||
g_object_get (binding, "control-source", &source, NULL);
|
||
}
|
||
|
||
REPORT_UNLESS (GST_IS_TIMED_VALUE_CONTROL_SOURCE (source), err,
|
||
"Could not find timed value control source on %s", field);
|
||
|
||
REPORT_UNLESS (gst_timed_value_control_source_set (source, timestamp, value),
|
||
err, "Could not set %s=%f at %" GST_TIME_FORMAT, field, value,
|
||
GST_TIME_ARGS (timestamp));
|
||
|
||
gst_object_unref (obj);
|
||
gst_structure_set (structure, "__res__", G_TYPE_INT, res, NULL);
|
||
|
||
return TRUE;
|
||
|
||
err:
|
||
gst_clear_object (&obj);
|
||
gst_structure_set (structure, "__res__", G_TYPE_INT,
|
||
GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED, NULL);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_set_timed_value_property (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
|
||
gst_structure_set (action->structure, "__action__", G_TYPE_POINTER,
|
||
action, "__scenario__", G_TYPE_POINTER, scenario, NULL);
|
||
|
||
gst_structure_foreach_id_str (action->structure,
|
||
(GstStructureForeachIdStrFunc) _set_timed_value, action->structure);
|
||
gst_structure_get_int (action->structure, "__res__", &res);
|
||
gst_structure_remove_fields (action->structure, "__action__", "__scenario__",
|
||
"__res__", NULL);
|
||
|
||
return res;
|
||
}
|
||
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_check_property (GstValidateScenario * scenario, GstValidateAction * action,
|
||
gpointer object, const gchar * propname, const GValue * expected_value,
|
||
gboolean report_error)
|
||
{
|
||
GValue cvalue = G_VALUE_INIT;
|
||
|
||
g_value_init (&cvalue, G_VALUE_TYPE (expected_value));
|
||
g_object_get_property (object, propname, &cvalue);
|
||
|
||
if (gst_value_compare (&cvalue, expected_value) != GST_VALUE_EQUAL) {
|
||
if (!report_error)
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
|
||
gchar *expected = gst_value_serialize (expected_value), *observed =
|
||
gst_value_serialize (&cvalue);
|
||
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"%" GST_PTR_FORMAT
|
||
"::%s expected value: '(%s)%s' different than observed: '(%s)%s'",
|
||
object, propname, G_VALUE_TYPE_NAME (&cvalue), expected,
|
||
G_VALUE_TYPE_NAME (expected_value), observed);
|
||
|
||
g_free (expected);
|
||
g_free (observed);
|
||
|
||
g_value_reset (&cvalue);
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
g_value_reset (&cvalue);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_set_or_check_properties (const GstIdStr * fieldname, const GValue * value,
|
||
GstStructure * structure)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
GstValidateScenario *scenario;
|
||
GstValidateAction *action;
|
||
GstObject *obj = NULL;
|
||
GParamSpec *paramspec = NULL;
|
||
gboolean no_value_check = FALSE;
|
||
GstValidateObjectSetPropertyFlags flags = 0;
|
||
const gchar *field = gst_id_str_as_str (fieldname);
|
||
const gchar *unused_fields[] = { "__scenario__", "__action__", "__res__",
|
||
"playback-time", "repeat", "no-value-check", NULL
|
||
};
|
||
|
||
if (g_strv_contains (unused_fields, field))
|
||
return TRUE;
|
||
|
||
gst_structure_get (structure, "__scenario__", G_TYPE_POINTER, &scenario,
|
||
"__action__", G_TYPE_POINTER, &action, NULL);
|
||
|
||
gst_structure_get_boolean (structure, "no-value-check", &no_value_check);
|
||
|
||
if (no_value_check) {
|
||
flags |= GST_VALIDATE_OBJECT_SET_PROPERTY_FLAGS_NO_VALUE_CHECK;
|
||
}
|
||
if (action->priv->optional)
|
||
flags |= GST_VALIDATE_OBJECT_SET_PROPERTY_FLAGS_OPTIONAL;
|
||
|
||
obj = _get_target_object_property (scenario, action, field, ¶mspec);
|
||
if (!obj || !paramspec) {
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
if (gst_structure_has_name (action->structure, "set-properties"))
|
||
res =
|
||
gst_validate_object_set_property_full (GST_VALIDATE_REPORTER (scenario),
|
||
G_OBJECT (obj), paramspec->name, value, flags);
|
||
else
|
||
res = _check_property (scenario, action, obj, paramspec->name, value, TRUE);
|
||
|
||
done:
|
||
gst_clear_object (&obj);
|
||
if (!gst_structure_has_field (structure, "__res__")
|
||
|| res != GST_VALIDATE_EXECUTE_ACTION_OK)
|
||
gst_structure_set (structure, "__res__", G_TYPE_INT, res, NULL);
|
||
return TRUE;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_set_or_check_properties (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
|
||
gst_structure_set (action->structure, "__action__", G_TYPE_POINTER,
|
||
action, "__scenario__", G_TYPE_POINTER, scenario, NULL);
|
||
|
||
gst_structure_foreach_id_str (action->structure,
|
||
(GstStructureForeachIdStrFunc) _set_or_check_properties,
|
||
action->structure);
|
||
gst_structure_get_int (action->structure, "__res__", &res);
|
||
gst_structure_remove_fields (action->structure, "__action__", "__scenario__",
|
||
"__res__", NULL);
|
||
|
||
return res;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_set_state (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
guint state;
|
||
const gchar *str_state;
|
||
GstStateChangeReturn ret;
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
g_return_val_if_fail ((str_state =
|
||
gst_structure_get_string (action->structure, "state")), FALSE);
|
||
|
||
g_return_val_if_fail (gst_validate_utils_enum_from_str (GST_TYPE_STATE,
|
||
str_state, &state), FALSE);
|
||
|
||
|
||
scenario->priv->target_state = state;
|
||
scenario->priv->changing_state = TRUE;
|
||
scenario->priv->seeked_in_pause = FALSE;
|
||
|
||
ret = gst_element_set_state (pipeline, state);
|
||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||
scenario->priv->changing_state = FALSE;
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action, STATE_CHANGE_FAILURE,
|
||
"Failed to set state to %s", str_state);
|
||
|
||
/* Nothing async on failure, action will be removed automatically */
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
goto done;
|
||
} else if (ret == GST_STATE_CHANGE_ASYNC) {
|
||
|
||
scenario->priv->needs_async_done = TRUE;
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
|
||
goto done;
|
||
}
|
||
|
||
scenario->priv->changing_state = FALSE;
|
||
|
||
done:
|
||
gst_object_unref (pipeline);
|
||
|
||
return res;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_pause (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
GstClockTime duration = 0;
|
||
GstValidateExecuteActionReturn ret;
|
||
|
||
gst_validate_action_get_clocktime (scenario, action, "duration", &duration);
|
||
gst_structure_set (action->structure, "state", G_TYPE_STRING, "paused", NULL);
|
||
|
||
GST_INFO_OBJECT (scenario, "Pausing for %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (duration));
|
||
|
||
ret = _execute_set_state (scenario, action);
|
||
|
||
if (ret != GST_VALIDATE_EXECUTE_ACTION_ERROR && duration)
|
||
g_timeout_add (GST_TIME_AS_MSECONDS (duration),
|
||
(GSourceFunc) _pause_action_restore_playing, scenario);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_play (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
GST_DEBUG ("Playing back");
|
||
|
||
gst_structure_set (action->structure, "state", G_TYPE_STRING,
|
||
"playing", NULL);
|
||
|
||
|
||
return _execute_set_state (scenario, action);
|
||
}
|
||
|
||
static gboolean
|
||
_action_sets_state (GstValidateAction * action)
|
||
{
|
||
if (action == NULL)
|
||
return FALSE;
|
||
|
||
if (g_strcmp0 (action->type, "set-state") == 0)
|
||
return TRUE;
|
||
|
||
if (g_strcmp0 (action->type, "play") == 0)
|
||
return TRUE;
|
||
|
||
if (g_strcmp0 (action->type, "pause") == 0)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_check_dropped (GstValidateScenario * scenario)
|
||
{
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
|
||
if (priv->max_dropped == -1 || priv->dropped == -1)
|
||
return;
|
||
|
||
GST_DEBUG_OBJECT (scenario, "Number of dropped buffers: %d (max allowed: %d)",
|
||
priv->dropped, priv->max_dropped);
|
||
|
||
if (priv->dropped > priv->max_dropped) {
|
||
GST_VALIDATE_REPORT (scenario, CONFIG_TOO_MANY_BUFFERS_DROPPED,
|
||
"Too many buffers have been dropped: %d (max allowed: %d)",
|
||
priv->dropped, priv->max_dropped);
|
||
}
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_eos (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
gboolean ret;
|
||
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
GST_DEBUG ("Sending EOS to pipeline at %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (action->playback_time));
|
||
|
||
ret = gst_element_send_event (pipeline, gst_event_new_eos ());
|
||
gst_object_unref (pipeline);
|
||
|
||
return ret ? GST_VALIDATE_EXECUTE_ACTION_OK :
|
||
GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
}
|
||
|
||
static int
|
||
find_input_selector (GValue * velement, const gchar * type)
|
||
{
|
||
GstElement *element = g_value_get_object (velement);
|
||
int result = !0;
|
||
|
||
if (G_OBJECT_TYPE (element) == g_type_from_name ("GstInputSelector")) {
|
||
GstPad *srcpad = gst_element_get_static_pad (element, "src");
|
||
|
||
if (srcpad) {
|
||
GstCaps *caps = gst_pad_query_caps (srcpad, NULL);
|
||
|
||
if (caps) {
|
||
const char *mime =
|
||
gst_structure_get_name (gst_caps_get_structure (caps, 0));
|
||
gboolean found = FALSE;
|
||
|
||
if (g_strcmp0 (type, "audio") == 0)
|
||
found = g_str_has_prefix (mime, "audio/");
|
||
else if (g_strcmp0 (type, "video") == 0)
|
||
found = g_str_has_prefix (mime, "video/")
|
||
&& !g_str_has_prefix (mime, "video/x-dvd-subpicture");
|
||
else if (g_strcmp0 (type, "text") == 0)
|
||
found = g_str_has_prefix (mime, "text/")
|
||
|| g_str_has_prefix (mime, "subtitle/")
|
||
|| g_str_has_prefix (mime, "video/x-dvd-subpicture");
|
||
|
||
if (found)
|
||
result = 0;
|
||
}
|
||
|
||
gst_caps_unref (caps);
|
||
gst_object_unref (srcpad);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static GstElement *
|
||
find_input_selector_with_type (GstBin * bin, const gchar * type)
|
||
{
|
||
GValue result = { 0, };
|
||
GstElement *input_selector = NULL;
|
||
GstIterator *iterator = gst_bin_iterate_recurse (bin);
|
||
|
||
if (gst_iterator_find_custom (iterator,
|
||
(GCompareFunc) find_input_selector, &result, (gpointer) type)) {
|
||
input_selector = g_value_get_object (&result);
|
||
}
|
||
gst_iterator_free (iterator);
|
||
|
||
return input_selector;
|
||
}
|
||
|
||
static GstPad *
|
||
find_nth_sink_pad (GstElement * element, int index)
|
||
{
|
||
GstIterator *iterator;
|
||
gboolean done = FALSE;
|
||
GstPad *pad = NULL;
|
||
int dec_index = index;
|
||
GValue data = { 0, };
|
||
|
||
iterator = gst_element_iterate_sink_pads (element);
|
||
while (!done) {
|
||
switch (gst_iterator_next (iterator, &data)) {
|
||
case GST_ITERATOR_OK:
|
||
if (!dec_index--) {
|
||
done = TRUE;
|
||
pad = g_value_get_object (&data);
|
||
break;
|
||
}
|
||
g_value_reset (&data);
|
||
break;
|
||
case GST_ITERATOR_RESYNC:
|
||
gst_iterator_resync (iterator);
|
||
dec_index = index;
|
||
break;
|
||
case GST_ITERATOR_ERROR:
|
||
done = TRUE;
|
||
break;
|
||
case GST_ITERATOR_DONE:
|
||
done = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
gst_iterator_free (iterator);
|
||
return pad;
|
||
}
|
||
|
||
static int
|
||
find_sink_pad_index (GstElement * element, GstPad * pad)
|
||
{
|
||
GstIterator *iterator;
|
||
gboolean done = FALSE;
|
||
int index = 0;
|
||
GValue data = { 0, };
|
||
|
||
iterator = gst_element_iterate_sink_pads (element);
|
||
while (!done) {
|
||
switch (gst_iterator_next (iterator, &data)) {
|
||
case GST_ITERATOR_OK:
|
||
if (pad == g_value_get_object (&data)) {
|
||
done = TRUE;
|
||
} else {
|
||
index++;
|
||
}
|
||
g_value_reset (&data);
|
||
break;
|
||
case GST_ITERATOR_RESYNC:
|
||
gst_iterator_resync (iterator);
|
||
index = 0;
|
||
break;
|
||
case GST_ITERATOR_ERROR:
|
||
done = TRUE;
|
||
break;
|
||
case GST_ITERATOR_DONE:
|
||
done = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
gst_iterator_free (iterator);
|
||
return index;
|
||
}
|
||
|
||
static GstPadProbeReturn
|
||
_check_select_pad_done (GstPad * pad, GstPadProbeInfo * info,
|
||
GstValidateAction * action)
|
||
{
|
||
if (GST_BUFFER_FLAG_IS_SET (info->data, GST_BUFFER_FLAG_DISCONT)) {
|
||
gst_validate_action_set_done (action);
|
||
|
||
return GST_PAD_PROBE_REMOVE;
|
||
}
|
||
|
||
return GST_PAD_PROBE_OK;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
execute_switch_track_default (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
guint index;
|
||
gboolean relative = FALSE;
|
||
const gchar *type, *str_index;
|
||
GstElement *input_selector;
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
if (!(type = gst_structure_get_string (action->structure, "type")))
|
||
type = "audio";
|
||
|
||
/* First find an input selector that has the right type */
|
||
input_selector = find_input_selector_with_type (GST_BIN (pipeline), type);
|
||
REPORT_UNLESS (input_selector, done,
|
||
"Could not find input selector for type %s", type);
|
||
GstState state, next;
|
||
GstPad *pad, *cpad, *srcpad;
|
||
|
||
str_index = gst_structure_get_string (action->structure, "index");
|
||
|
||
if (str_index == NULL) {
|
||
if (!gst_structure_get_uint (action->structure, "index", &index)) {
|
||
GST_WARNING ("No index given, defaulting to +1");
|
||
index = 1;
|
||
relative = TRUE;
|
||
}
|
||
} else {
|
||
relative = strchr ("+-", str_index[0]) != NULL;
|
||
index = g_ascii_strtoll (str_index, NULL, 10);
|
||
}
|
||
|
||
if (relative) { /* We are changing track relatively to current track */
|
||
int npads;
|
||
|
||
g_object_get (input_selector, "active-pad", &pad, "n-pads", &npads, NULL);
|
||
if (pad) {
|
||
int current_index = find_sink_pad_index (input_selector, pad);
|
||
|
||
index = (current_index + index) % npads;
|
||
gst_object_unref (pad);
|
||
}
|
||
}
|
||
|
||
pad = find_nth_sink_pad (input_selector, index);
|
||
g_object_get (input_selector, "active-pad", &cpad, NULL);
|
||
if (gst_element_get_state (pipeline, &state, &next, 0) &&
|
||
state == GST_STATE_PLAYING && next == GST_STATE_VOID_PENDING) {
|
||
srcpad = gst_element_get_static_pad (input_selector, "src");
|
||
|
||
gst_pad_add_probe (srcpad,
|
||
GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST,
|
||
(GstPadProbeCallback) _check_select_pad_done, action, NULL);
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
gst_object_unref (srcpad);
|
||
}
|
||
|
||
g_object_set (input_selector, "active-pad", pad, NULL);
|
||
gst_object_unref (pad);
|
||
gst_object_unref (cpad);
|
||
gst_object_unref (input_selector);
|
||
|
||
/* No selector found -> Failed */
|
||
done:
|
||
gst_object_unref (pipeline);
|
||
|
||
return res;
|
||
}
|
||
|
||
static GstPadProbeReturn
|
||
_check_pad_event_selection_done (GstPad * pad, GstPadProbeInfo * info,
|
||
GstValidateAction * action)
|
||
{
|
||
if (GST_EVENT_TYPE (info->data) == GST_EVENT_STREAM_START) {
|
||
gst_validate_action_set_done (action);
|
||
return GST_PAD_PROBE_REMOVE;
|
||
}
|
||
return GST_PAD_PROBE_OK;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
execute_switch_track_pb (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
gint index, n;
|
||
const gchar *type, *str_index;
|
||
|
||
gint flags, current, tflag;
|
||
gchar *tmp, *current_txt;
|
||
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
gboolean relative = FALSE, disabling = FALSE;
|
||
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
if (!(type = gst_structure_get_string (action->structure, "type")))
|
||
type = "audio";
|
||
|
||
tflag = gst_validate_utils_flags_from_str (g_type_from_name ("GstPlayFlags"),
|
||
type);
|
||
current_txt = g_strdup_printf ("current-%s", type);
|
||
|
||
tmp = g_strdup_printf ("n-%s", type);
|
||
g_object_get (pipeline, "flags", &flags, tmp, &n, current_txt, ¤t,
|
||
NULL);
|
||
|
||
/* Don't try to use -1 */
|
||
if (current == -1)
|
||
current = 0;
|
||
|
||
g_free (tmp);
|
||
|
||
if (gst_structure_has_field (action->structure, "disable")) {
|
||
disabling = TRUE;
|
||
flags &= ~tflag;
|
||
index = -1;
|
||
} else if (!(str_index =
|
||
gst_structure_get_string (action->structure, "index"))) {
|
||
if (!gst_structure_get_int (action->structure, "index", &index)) {
|
||
GST_WARNING ("No index given, defaulting to +1");
|
||
index = 1;
|
||
relative = TRUE;
|
||
}
|
||
} else {
|
||
relative = strchr ("+-", str_index[0]) != NULL;
|
||
index = g_ascii_strtoll (str_index, NULL, 10);
|
||
}
|
||
|
||
if (relative) { /* We are changing track relatively to current track */
|
||
REPORT_UNLESS (n != 0, done,
|
||
"Trying to execute a relative %s for %s track when there"
|
||
" is no track of this type available on current stream.",
|
||
action->type, type);
|
||
|
||
index = (current + index) % n;
|
||
}
|
||
|
||
if (!disabling) {
|
||
GstState state, next;
|
||
GstPad *oldpad, *newpad;
|
||
tmp = g_strdup_printf ("get-%s-pad", type);
|
||
g_signal_emit_by_name (G_OBJECT (pipeline), tmp, current, &oldpad);
|
||
g_signal_emit_by_name (G_OBJECT (pipeline), tmp, index, &newpad);
|
||
|
||
gst_validate_printf (action, "Switching to track number: %i,"
|
||
" (from %s:%s to %s:%s)\n", index, GST_DEBUG_PAD_NAME (oldpad),
|
||
GST_DEBUG_PAD_NAME (newpad));
|
||
flags |= tflag;
|
||
g_free (tmp);
|
||
|
||
if (gst_element_get_state (pipeline, &state, &next, 0) &&
|
||
state == GST_STATE_PLAYING && next == GST_STATE_VOID_PENDING) {
|
||
GstPad *srcpad = NULL;
|
||
GstElement *combiner = NULL;
|
||
if (newpad == oldpad) {
|
||
srcpad = gst_pad_get_peer (oldpad);
|
||
} else if (newpad) {
|
||
combiner = GST_ELEMENT (gst_object_get_parent (GST_OBJECT (newpad)));
|
||
if (combiner) {
|
||
srcpad = gst_element_get_static_pad (combiner, "src");
|
||
gst_object_unref (combiner);
|
||
}
|
||
}
|
||
|
||
if (srcpad) {
|
||
gst_pad_add_probe (srcpad,
|
||
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
|
||
(GstPadProbeCallback) _check_pad_event_selection_done, action,
|
||
NULL);
|
||
gst_object_unref (srcpad);
|
||
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
} else
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
}
|
||
|
||
if (oldpad)
|
||
gst_object_unref (oldpad);
|
||
gst_object_unref (newpad);
|
||
} else {
|
||
gst_validate_printf (action, "Disabling track type %s", type);
|
||
}
|
||
|
||
g_object_set (pipeline, "flags", flags, current_txt, index, NULL);
|
||
g_free (current_txt);
|
||
|
||
done:
|
||
gst_object_unref (pipeline);
|
||
return res;
|
||
}
|
||
|
||
static GstStreamType
|
||
stream_type_from_string (const gchar * type)
|
||
{
|
||
if (!g_strcmp0 (type, "video"))
|
||
return GST_STREAM_TYPE_VIDEO;
|
||
else if (!g_strcmp0 (type, "text"))
|
||
return GST_STREAM_TYPE_TEXT;
|
||
|
||
/* default */
|
||
return GST_STREAM_TYPE_AUDIO;
|
||
}
|
||
|
||
/* Return a list of stream ID all the currently selected streams but the ones
|
||
* of type @type */
|
||
static GList *
|
||
disable_stream (GstValidatePipelineMonitor * monitor, GstStreamType type)
|
||
{
|
||
GList *streams = NULL, *l;
|
||
|
||
for (l = monitor->streams_selected; l; l = g_list_next (l)) {
|
||
GstStream *s = l->data;
|
||
|
||
if (gst_stream_get_stream_type (s) != type) {
|
||
streams = g_list_append (streams, (gpointer) s->stream_id);
|
||
}
|
||
}
|
||
|
||
return streams;
|
||
}
|
||
|
||
static GList *
|
||
switch_stream (GstValidatePipelineMonitor * monitor, GstValidateAction * action,
|
||
GstStreamType type, gint index, gboolean relative)
|
||
{
|
||
guint nb_streams;
|
||
guint i, n = 0, current = 0;
|
||
GList *result = NULL, *l;
|
||
GstStream *streams[256], *s, *current_stream = NULL;
|
||
|
||
/* Keep all streams which are not @type */
|
||
for (l = monitor->streams_selected; l; l = g_list_next (l)) {
|
||
s = l->data;
|
||
|
||
if (gst_stream_get_stream_type (s) != type) {
|
||
result = g_list_append (result, (gpointer) s->stream_id);
|
||
} else if (!current_stream) {
|
||
/* Assume the stream we want to switch from is the first one */
|
||
current_stream = s;
|
||
}
|
||
}
|
||
|
||
/* Calculate the number of @type streams */
|
||
nb_streams = gst_stream_collection_get_size (monitor->stream_collection);
|
||
for (i = 0; i < nb_streams; i++) {
|
||
s = gst_stream_collection_get_stream (monitor->stream_collection, i);
|
||
|
||
if (gst_stream_get_stream_type (s) == type) {
|
||
streams[n] = s;
|
||
|
||
if (current_stream
|
||
&& !g_strcmp0 (s->stream_id, current_stream->stream_id))
|
||
current = n;
|
||
|
||
n++;
|
||
}
|
||
}
|
||
|
||
if (G_UNLIKELY (n == 0)) {
|
||
GST_ERROR ("No streams available of the required type");
|
||
return result;
|
||
}
|
||
|
||
if (relative) { /* We are changing track relatively to current track */
|
||
index = (current + index) % n;
|
||
} else
|
||
index %= n;
|
||
|
||
/* Add the new stream we want to switch to */
|
||
s = streams[index];
|
||
|
||
gst_validate_printf (action, "Switching from stream %s to %s",
|
||
current_stream ? current_stream->stream_id : "", s->stream_id);
|
||
|
||
return g_list_append (result, (gpointer) s->stream_id);
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
execute_switch_track_pb3 (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
gint index;
|
||
GstStreamType stype;
|
||
const gchar *type, *str_index;
|
||
GList *new_streams = NULL;
|
||
GstValidatePipelineMonitor *monitor;
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
monitor = (GstValidatePipelineMonitor *) (g_object_get_data ((GObject *)
|
||
pipeline, "validate-monitor"));
|
||
|
||
if (!monitor->stream_collection) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"No stream collection message received on the bus, "
|
||
"can not switch track.");
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
if (!monitor->streams_selected) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"No streams selected message received on the bus");
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
type = gst_structure_get_string (action->structure, "type");
|
||
stype = stream_type_from_string (type);
|
||
|
||
if (gst_structure_has_field (action->structure, "disable")) {
|
||
gst_validate_printf (action, "Disabling track type %s", type);
|
||
new_streams = disable_stream (monitor, stype);
|
||
} else {
|
||
gboolean relative = FALSE;
|
||
|
||
if (!(str_index = gst_structure_get_string (action->structure, "index"))) {
|
||
if (!gst_structure_get_int (action->structure, "index", &index)) {
|
||
GST_WARNING ("No index given, defaulting to +1");
|
||
index = 1;
|
||
relative = TRUE;
|
||
}
|
||
} else {
|
||
relative = strchr ("+-", str_index[0]) != NULL;
|
||
index = g_ascii_strtoll (str_index, NULL, 10);
|
||
}
|
||
|
||
new_streams = switch_stream (monitor, action, stype, index, relative);
|
||
}
|
||
|
||
gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (action),
|
||
ACTION_EXPECTED_STREAM_QUARK, g_list_copy (new_streams),
|
||
(GDestroyNotify) g_list_free);
|
||
|
||
if (!gst_element_send_event (pipeline,
|
||
gst_event_new_select_streams (new_streams))) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "select-streams event not handled");
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
priv->pending_switch_track = action;
|
||
if (scenario->priv->target_state > GST_STATE_PAUSED) {
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
} else {
|
||
gst_validate_action_ref (action);
|
||
res = GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING;
|
||
}
|
||
|
||
done:
|
||
gst_object_unref (pipeline);
|
||
|
||
return res;
|
||
}
|
||
|
||
typedef struct
|
||
{
|
||
GstValidateAction *action;
|
||
GRecMutex m;
|
||
gulong message_sid;
|
||
gulong stopping_sid;
|
||
|
||
GList *wanted_streams;
|
||
|
||
|
||
gint wanted_n_calls;
|
||
gint n_calls;
|
||
|
||
} SelectStreamData;
|
||
|
||
static SelectStreamData *
|
||
select_stream_data_new (GstValidateAction * action)
|
||
{
|
||
SelectStreamData *d = g_atomic_rc_box_new0 (SelectStreamData);
|
||
|
||
d->action = gst_validate_action_ref (action);
|
||
if (!gst_structure_get_int (action->structure, "n-calls", &d->wanted_n_calls)) {
|
||
d->wanted_n_calls = 1;
|
||
}
|
||
|
||
if (d->wanted_n_calls < -1) {
|
||
gst_validate_error_structure (action,
|
||
"`n-calls` in %" GST_PTR_FORMAT " can not be < -1, got %d",
|
||
action->structure, d->wanted_n_calls);
|
||
}
|
||
|
||
return d;
|
||
}
|
||
|
||
static void
|
||
select_stream_data_free (SelectStreamData * d)
|
||
{
|
||
gst_validate_action_unref (d->action);
|
||
g_list_free_full (d->wanted_streams, g_free);
|
||
g_rec_mutex_clear (&d->m);
|
||
}
|
||
|
||
static void
|
||
select_stream_data_unref (SelectStreamData * d)
|
||
{
|
||
g_atomic_rc_box_release_full (d, (GDestroyNotify) select_stream_data_free);
|
||
}
|
||
|
||
static void
|
||
stream_selection_cb (GstBus * bus, GstMessage * message, SelectStreamData * d)
|
||
{
|
||
GstValidateScenario *scenario = NULL;
|
||
GstStreamCollection *collection = NULL, *selected_streams = NULL;
|
||
GList *streams = NULL;
|
||
|
||
switch (GST_MESSAGE_TYPE (message)) {
|
||
case GST_MESSAGE_STREAM_COLLECTION:
|
||
/* Handle the case were we have 2 StreamCollection message happennning at the
|
||
* same time on the bus */
|
||
g_rec_mutex_lock (&d->m);
|
||
scenario = gst_validate_action_get_scenario (d->action);
|
||
gst_message_parse_stream_collection (message, &collection);
|
||
g_assert (collection);
|
||
break;
|
||
case GST_MESSAGE_STREAMS_SELECTED:
|
||
g_rec_mutex_lock (&d->m);
|
||
scenario = gst_validate_action_get_scenario (d->action);
|
||
gst_message_parse_streams_selected (message, &selected_streams);
|
||
g_assert (selected_streams);
|
||
goto done;
|
||
default:
|
||
return;
|
||
}
|
||
|
||
const GValue *v = gst_structure_get_value (d->action->structure, "indexes");
|
||
if (G_VALUE_HOLDS_INT (v)) {
|
||
GstStream *stream =
|
||
gst_stream_collection_get_stream (collection, g_value_get_int (v));
|
||
|
||
if (!stream) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, d->action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not find stream with index %d in %" GST_PTR_FORMAT,
|
||
g_value_get_int (v), collection);
|
||
goto done;
|
||
}
|
||
|
||
streams =
|
||
g_list_append (streams, g_strdup (gst_stream_get_stream_id (stream)));
|
||
} else if (GST_VALUE_HOLDS_ARRAY (v)) {
|
||
for (gint i = 0; i < gst_value_array_get_size (v); i++) {
|
||
const GValue *ivalue = gst_value_array_get_value (v, i);
|
||
|
||
if (!G_VALUE_HOLDS_INT (ivalue)) {
|
||
gst_validate_error_structure (d->action,
|
||
"Could not parse `indexes` in %" GST_PTR_FORMAT,
|
||
d->action->structure);
|
||
goto done;
|
||
}
|
||
|
||
GstStream *stream = gst_stream_collection_get_stream (collection,
|
||
g_value_get_int (ivalue));
|
||
if (!stream) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, d->action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not find stream with index %d in %" GST_PTR_FORMAT,
|
||
g_value_get_int (ivalue), collection);
|
||
goto done;
|
||
}
|
||
streams =
|
||
g_list_append (streams, g_strdup (gst_stream_get_stream_id (stream)));
|
||
}
|
||
} else {
|
||
gst_validate_error_structure (d->action,
|
||
"Could not parse `indexes` in %" GST_PTR_FORMAT, d->action->structure);
|
||
goto done;
|
||
}
|
||
|
||
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
if (pipeline == NULL) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, d->action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Can't execute a '%s' action after the pipeline " "has been destroyed.",
|
||
d->action->type);
|
||
|
||
goto done;
|
||
}
|
||
|
||
if (!gst_element_send_event (GST_ELEMENT (GST_MESSAGE_SRC (message)),
|
||
gst_event_new_select_streams (streams))) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, d->action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not send `SELECT_STREAM` event!");
|
||
}
|
||
|
||
g_list_free_full (d->wanted_streams, g_free);
|
||
d->wanted_streams = streams;
|
||
d->n_calls += 1;
|
||
|
||
done:
|
||
if (selected_streams && d->message_sid &&
|
||
d->wanted_n_calls >= 1 && d->n_calls == d->wanted_n_calls) {
|
||
/* Consider action done once we get the STREAM_SELECTED signal */
|
||
gst_validate_action_set_done (d->action);
|
||
gst_bus_disable_sync_message_emission (bus);
|
||
g_signal_handler_disconnect (bus, d->message_sid);
|
||
d->message_sid = 0;
|
||
|
||
if (d->stopping_sid) {
|
||
g_signal_handler_disconnect (scenario, d->stopping_sid);
|
||
d->stopping_sid = 0;
|
||
}
|
||
}
|
||
|
||
gst_clear_object (&scenario);
|
||
gst_clear_object (&collection);
|
||
|
||
g_rec_mutex_unlock (&d->m);
|
||
}
|
||
|
||
static void
|
||
stream_selection_scenario_stopping_cb (GstValidateScenario * scenario,
|
||
SelectStreamData * d)
|
||
{
|
||
g_rec_mutex_lock (&d->m);
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
GstBus *bus = NULL;
|
||
|
||
if (pipeline) {
|
||
bus = gst_element_get_bus (pipeline);
|
||
}
|
||
|
||
if (!((d->wanted_n_calls == 0 && d->n_calls > 0) || d->wanted_n_calls == -1)) {
|
||
gst_validate_report_action (GST_VALIDATE_REPORTER (scenario), d->action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Wrong number of calls: wanted %d got: %d",
|
||
d->wanted_n_calls, d->n_calls);
|
||
}
|
||
|
||
gst_validate_action_set_done (d->action);
|
||
|
||
if (bus && d->message_sid) {
|
||
gst_bus_disable_sync_message_emission (bus);
|
||
g_signal_handler_disconnect (bus, d->message_sid);
|
||
d->message_sid = 0;
|
||
}
|
||
|
||
if (d->stopping_sid) {
|
||
g_signal_handler_disconnect (scenario, d->stopping_sid);
|
||
d->stopping_sid = 0;
|
||
}
|
||
g_rec_mutex_unlock (&d->m);
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_select_streams (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
GstBus *bus = gst_element_get_bus (pipeline);
|
||
gst_bus_enable_sync_message_emission (bus);
|
||
|
||
SelectStreamData *d = select_stream_data_new (action);
|
||
/* Ensure that the data signal ID is set before the callback is called */
|
||
g_rec_mutex_lock (&d->m);
|
||
d->message_sid = g_signal_connect_data (bus,
|
||
"sync-message",
|
||
G_CALLBACK (stream_selection_cb),
|
||
d, (GClosureNotify) select_stream_data_unref, 0);
|
||
d->stopping_sid = g_signal_connect_data (scenario,
|
||
"stopping",
|
||
G_CALLBACK (stream_selection_scenario_stopping_cb),
|
||
g_atomic_rc_box_acquire (d),
|
||
(GClosureNotify) select_stream_data_unref, 0);
|
||
g_rec_mutex_unlock (&d->m);
|
||
|
||
gst_object_unref (bus);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_switch_track (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidatePipelineMonitor *monitor;
|
||
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
monitor = (GstValidatePipelineMonitor *) (g_object_get_data ((GObject *)
|
||
pipeline, "validate-monitor"));
|
||
gst_object_unref (pipeline);
|
||
|
||
if (monitor->is_playbin)
|
||
return execute_switch_track_pb (scenario, action);
|
||
else if (monitor->is_playbin3)
|
||
return execute_switch_track_pb3 (scenario, action);
|
||
|
||
return execute_switch_track_default (scenario, action);
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_set_rank_or_disable_feature (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
guint rank;
|
||
GList *features, *origlist;
|
||
GstPlugin *plugin;
|
||
GstPluginFeature *feature;
|
||
const gchar *name;
|
||
gboolean removing_feature =
|
||
gst_structure_has_name (action->structure, "remove-plugin-feature");
|
||
GstRegistry *registry = gst_registry_get ();
|
||
|
||
REPORT_UNLESS (
|
||
(name = gst_structure_get_string (action->structure, "feature-name")) ||
|
||
(name = gst_structure_get_string (action->structure, "name")), done,
|
||
"Could not find the name of the plugin/feature(s) to tweak");
|
||
|
||
if (removing_feature)
|
||
REPORT_UNLESS (
|
||
(gst_structure_get_uint (action->structure, "rank", &rank)) ||
|
||
(gst_structure_get_int (action->structure, "rank", (gint *) & rank)),
|
||
done, "Could not get rank to set on %s", name);
|
||
|
||
feature = gst_registry_lookup_feature (registry, name);
|
||
if (feature) {
|
||
if (removing_feature)
|
||
gst_plugin_feature_set_rank (feature, rank);
|
||
else
|
||
gst_registry_remove_feature (registry, feature);
|
||
gst_object_unref (feature);
|
||
|
||
goto done;
|
||
}
|
||
|
||
REPORT_UNLESS ((plugin = gst_registry_find_plugin (registry, name)),
|
||
done, "Could not find %s", name);
|
||
|
||
if (removing_feature) {
|
||
gst_registry_remove_plugin (registry, plugin);
|
||
goto done;
|
||
}
|
||
|
||
origlist = features =
|
||
gst_registry_get_feature_list_by_plugin (registry,
|
||
gst_plugin_get_name (plugin));
|
||
for (; features; features = features->next)
|
||
gst_plugin_feature_set_rank (features->data, rank);
|
||
gst_plugin_feature_list_free (origlist);
|
||
|
||
done:
|
||
return res;
|
||
}
|
||
|
||
static inline gboolean
|
||
_add_execute_actions_gsource (GstValidateScenario * scenario)
|
||
{
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
if (priv->execute_actions_source_id == 0 && priv->wait_id == 0
|
||
&& priv->signal_handler_id == 0 && priv->wait_message_action == NULL) {
|
||
if (!scenario->priv->action_execution_interval)
|
||
priv->execute_actions_source_id =
|
||
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
|
||
(GSourceFunc) execute_next_action,
|
||
gst_object_ref (GST_OBJECT_CAST (scenario)), gst_object_unref);
|
||
else
|
||
priv->execute_actions_source_id =
|
||
g_timeout_add_full (G_PRIORITY_DEFAULT,
|
||
scenario->priv->action_execution_interval,
|
||
(GSourceFunc) execute_next_action,
|
||
gst_object_ref (GST_OBJECT_CAST (scenario)), gst_object_unref);
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
GST_DEBUG_OBJECT (scenario, "Start checking position again");
|
||
return TRUE;
|
||
}
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
GST_LOG_OBJECT (scenario, "No need to start a new gsource");
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
_get_position (GstValidateScenario * scenario,
|
||
GstValidateAction * act, GstClockTime * position)
|
||
{
|
||
gboolean has_pos = FALSE, has_dur = FALSE;
|
||
GstClockTime duration = -1;
|
||
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
|
||
if (!pipeline) {
|
||
GST_ERROR_OBJECT (scenario, "No pipeline set anymore!");
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
has_pos = gst_element_query_position (pipeline, GST_FORMAT_TIME,
|
||
(gint64 *) position)
|
||
&& GST_CLOCK_TIME_IS_VALID (*position);
|
||
has_dur =
|
||
gst_element_query_duration (pipeline, GST_FORMAT_TIME,
|
||
(gint64 *) & duration)
|
||
&& GST_CLOCK_TIME_IS_VALID (duration);
|
||
|
||
if (!has_pos && GST_STATE (pipeline) >= GST_STATE_PAUSED &&
|
||
act && GST_CLOCK_TIME_IS_VALID (act->playback_time)) {
|
||
GST_INFO_OBJECT (scenario, "Unknown position: %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (*position));
|
||
|
||
goto fail;
|
||
}
|
||
|
||
if (has_pos && has_dur && !priv->got_eos) {
|
||
if (*position > duration) {
|
||
_add_execute_actions_gsource (scenario);
|
||
GST_VALIDATE_REPORT (scenario,
|
||
QUERY_POSITION_SUPERIOR_DURATION,
|
||
"Reported position %" GST_TIME_FORMAT " > reported duration %"
|
||
GST_TIME_FORMAT, GST_TIME_ARGS (*position), GST_TIME_ARGS (duration));
|
||
|
||
goto done;
|
||
}
|
||
}
|
||
|
||
done:
|
||
gst_object_unref (pipeline);
|
||
return TRUE;
|
||
|
||
fail:
|
||
gst_object_unref (pipeline);
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
_check_position (GstValidateScenario * scenario, GstValidateAction * act,
|
||
GstClockTime * position, gdouble * rate)
|
||
{
|
||
GstQuery *query;
|
||
|
||
GstClockTime start_with_tolerance, stop_with_tolerance;
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
GstElement *pipeline;
|
||
|
||
if (!_get_position (scenario, act, position))
|
||
return FALSE;
|
||
|
||
GST_DEBUG_OBJECT (scenario, "Current position: %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (*position));
|
||
|
||
/* Check if playback is within seek segment */
|
||
start_with_tolerance = (priv->segment_start <
|
||
priv->seek_pos_tol) ? 0 : priv->segment_start - priv->seek_pos_tol;
|
||
stop_with_tolerance =
|
||
GST_CLOCK_TIME_IS_VALID (priv->segment_stop) ? priv->segment_stop +
|
||
priv->seek_pos_tol : -1;
|
||
|
||
if (priv->ignore_invalid_positions) {
|
||
GST_DEBUG_OBJECT (scenario, "Ignoring invalid position");
|
||
} else if ((GST_CLOCK_TIME_IS_VALID (stop_with_tolerance)
|
||
&& *position > stop_with_tolerance)
|
||
|| (priv->seek_flags & GST_SEEK_FLAG_ACCURATE
|
||
&& *position < start_with_tolerance
|
||
&& priv->seek_format == GST_FORMAT_TIME)) {
|
||
|
||
GST_VALIDATE_REPORT_ACTION (scenario, act, QUERY_POSITION_OUT_OF_SEGMENT,
|
||
"Current position %" GST_TIME_FORMAT " not in the expected range [%"
|
||
GST_TIME_FORMAT " -- %" GST_TIME_FORMAT, GST_TIME_ARGS (*position),
|
||
GST_TIME_ARGS (start_with_tolerance),
|
||
GST_TIME_ARGS (stop_with_tolerance));
|
||
}
|
||
|
||
pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
if (pipeline == NULL) {
|
||
GST_INFO_OBJECT (scenario, "No pipeline set anymore");
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
query = gst_query_new_segment (GST_FORMAT_DEFAULT);
|
||
if (gst_element_query (GST_ELEMENT (pipeline), query))
|
||
gst_query_parse_segment (query, rate, NULL, NULL, NULL);
|
||
gst_query_unref (query);
|
||
gst_object_unref (pipeline);
|
||
|
||
if (priv->seeked_in_pause && priv->seek_flags & GST_SEEK_FLAG_ACCURATE &&
|
||
priv->seek_format == GST_FORMAT_TIME) {
|
||
if (*rate > 0
|
||
&& (GstClockTime) ABS (GST_CLOCK_DIFF (*position,
|
||
priv->segment_start)) > priv->seek_pos_tol) {
|
||
priv->seeked_in_pause = FALSE;
|
||
GST_VALIDATE_REPORT_ACTION (scenario, act,
|
||
EVENT_SEEK_RESULT_POSITION_WRONG,
|
||
"Reported position after accurate seek in PAUSED state should be exactly"
|
||
" what the user asked for. Position %" GST_TIME_FORMAT
|
||
" is not not the expected one: %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (*position), GST_TIME_ARGS (priv->segment_start));
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
_check_message_type (GstValidateScenario * scenario, GstValidateAction * act,
|
||
GstMessage * message)
|
||
{
|
||
return act && message
|
||
&& !g_strcmp0 (gst_structure_get_string (act->structure, "on-message"),
|
||
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
||
}
|
||
|
||
static gboolean
|
||
_should_execute_action (GstValidateScenario * scenario, GstValidateAction * act,
|
||
GstClockTime position, gdouble rate)
|
||
{
|
||
GstElement *pipeline = NULL;
|
||
|
||
if (!act) {
|
||
GST_DEBUG_OBJECT (scenario, "No action to execute");
|
||
|
||
goto no;
|
||
}
|
||
|
||
pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
if (pipeline == NULL) {
|
||
|
||
if (!(GST_VALIDATE_ACTION_GET_TYPE (act)->flags &
|
||
GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, act,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Trying to execute an %s action after the pipeline has been destroyed"
|
||
" but the type has not been marked as "
|
||
"GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE", act->type);
|
||
|
||
return FALSE;
|
||
} else if (GST_CLOCK_TIME_IS_VALID (act->playback_time)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, act,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Trying to execute action %s with playback time %" GST_TIME_FORMAT
|
||
" after the pipeline has been destroyed. It is impossible"
|
||
" to execute an action with a playback time specified"
|
||
" after the pipeline has been destroyed", act->type,
|
||
GST_TIME_ARGS (act->playback_time));
|
||
|
||
goto no;
|
||
}
|
||
|
||
GST_DEBUG_OBJECT (scenario, "No pipeline, go and execute action!");
|
||
|
||
goto yes;
|
||
} else if (scenario->priv->got_eos) {
|
||
GST_DEBUG_OBJECT (scenario, "Just got EOS go and execute next action!");
|
||
scenario->priv->got_eos = FALSE;
|
||
} else if (GST_STATE (pipeline) < GST_STATE_PAUSED) {
|
||
GST_DEBUG_OBJECT (scenario, "Pipeline not even in paused, "
|
||
"just executing actions");
|
||
|
||
goto yes;
|
||
} else if (act->playback_time == GST_CLOCK_TIME_NONE) {
|
||
GST_DEBUG_OBJECT (scenario, "No timing info, executing action");
|
||
|
||
goto yes;
|
||
} else if ((rate > 0 && (GstClockTime) position < act->playback_time)) {
|
||
GST_DEBUG_OBJECT (scenario, "positive rate and position %" GST_TIME_FORMAT
|
||
" < playback_time %" GST_TIME_FORMAT, GST_TIME_ARGS (position),
|
||
GST_TIME_ARGS (act->playback_time));
|
||
|
||
goto no;
|
||
} else if (rate < 0 && (GstClockTime) position > act->playback_time) {
|
||
GST_DEBUG_OBJECT (scenario, "negative rate and position %" GST_TIME_FORMAT
|
||
" < playback_time %" GST_TIME_FORMAT, GST_TIME_ARGS (position),
|
||
GST_TIME_ARGS (act->playback_time));
|
||
|
||
goto no;
|
||
}
|
||
|
||
yes:
|
||
gst_object_unref (pipeline);
|
||
return TRUE;
|
||
|
||
no:
|
||
gst_clear_object (&pipeline);
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
_set_action_playback_time (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
if (!gst_validate_action_get_clocktime (scenario, action,
|
||
"playback-time", &action->playback_time)) {
|
||
gst_validate_error_structure (action,
|
||
"Could not parse playback-time in %" GST_PTR_FORMAT, action->structure);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
gst_structure_set (action->structure, "playback-time", GST_TYPE_CLOCK_TIME,
|
||
action->playback_time, NULL);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
gst_validate_parse_next_action_playback_time (GstValidateScenario * self)
|
||
{
|
||
GstValidateAction *action;
|
||
GstValidateScenarioPrivate *priv = self->priv;
|
||
|
||
if (!priv->actions)
|
||
return TRUE;
|
||
|
||
action = (GstValidateAction *) priv->actions->data;
|
||
if (!action->priv->needs_playback_parsing)
|
||
return TRUE;
|
||
|
||
if (!_set_action_playback_time (self, action)) {
|
||
GST_ERROR_OBJECT (self, "Could not set playback_time!");
|
||
|
||
return FALSE;
|
||
}
|
||
action->priv->needs_playback_parsing = FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
_foreach_find_iterator (const GstIdStr * fieldname, GValue * value,
|
||
GstValidateAction * action)
|
||
{
|
||
const gchar *field = gst_id_str_as_str (fieldname);
|
||
|
||
if (!g_strcmp0 (field, "actions"))
|
||
return TRUE;
|
||
|
||
if (!GST_VALUE_HOLDS_INT_RANGE (value) && !GST_VALUE_HOLDS_ARRAY (value)) {
|
||
gst_validate_error_structure (action,
|
||
"Unsupported iterator type `%s` for %s"
|
||
". Only ranges (`[(int)start, (int)stop, [(int)step]]`) and arrays "
|
||
" (`<item1, item2>`) are supported", field, G_VALUE_TYPE_NAME (value));
|
||
return TRUE;
|
||
}
|
||
|
||
if (GST_VALIDATE_ACTION_RANGE_NAME (action)) {
|
||
gst_validate_error_structure (action, "Wrong iterator syntax, "
|
||
" only one iterator field is supported.");
|
||
return FALSE;
|
||
}
|
||
|
||
GST_VALIDATE_ACTION_RANGE_NAME (action) = g_strdup (field);
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
static GstValidateExecuteActionReturn
|
||
gst_validate_action_get_execution_scenario (GstValidateAction * action,
|
||
GstValidateScenario ** target_scenario)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
GstElement *sub_pipeline = NULL;
|
||
GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
|
||
|
||
g_assert (scenario);
|
||
|
||
const gchar *sub_scenario_name =
|
||
gst_structure_get_string (action->structure, "pipeline-name");
|
||
g_assert (sub_scenario_name);
|
||
sub_pipeline =
|
||
gst_validate_scenario_get_sub_pipeline (scenario, sub_scenario_name);
|
||
|
||
REPORT_UNLESS (sub_pipeline, err, "Could not find sub-pipeline %s",
|
||
sub_scenario_name);
|
||
|
||
GstValidateBinMonitor *subbin_monitor = NULL;
|
||
subbin_monitor =
|
||
GST_VALIDATE_BIN_MONITOR (gst_validate_get_monitor (G_OBJECT
|
||
(sub_pipeline)));
|
||
REPORT_UNLESS (subbin_monitor->scenario, err,
|
||
"Sub pipeline doesn't have a scenario");
|
||
GST_DEBUG_OBJECT (scenario, "Action %s running on scenario %" GST_PTR_FORMAT,
|
||
action->type, subbin_monitor->scenario);
|
||
gst_object_unref (scenario);
|
||
*target_scenario = gst_object_ref (subbin_monitor->scenario);
|
||
|
||
done:
|
||
g_clear_object (&sub_pipeline);
|
||
|
||
return res;
|
||
|
||
err:
|
||
g_clear_object (&scenario);
|
||
g_clear_object (target_scenario);
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
typedef struct
|
||
{
|
||
gulong subaction_done_sigid;
|
||
GstValidateAction *action;
|
||
GMutex sigid_lock;
|
||
} ValidateActionForeignScenarioData;
|
||
|
||
static void
|
||
validate_action_foreign_scenario_data_clear (ValidateActionForeignScenarioData *
|
||
data)
|
||
{
|
||
gst_validate_action_unref (data->action);
|
||
}
|
||
|
||
static void
|
||
validate_action_foreign_scenario_data_unref (ValidateActionForeignScenarioData *
|
||
data)
|
||
{
|
||
g_atomic_rc_box_release_full (data,
|
||
(GDestroyNotify) validate_action_foreign_scenario_data_clear);
|
||
}
|
||
|
||
|
||
static void
|
||
gst_validate_foreign_subaction_done_cb (GstValidateScenario *
|
||
execution_scenario, GstValidateAction * sub_action,
|
||
ValidateActionForeignScenarioData * data)
|
||
{
|
||
gst_validate_action_set_done (data->action);
|
||
|
||
g_mutex_lock (&data->sigid_lock);
|
||
if (data->subaction_done_sigid) {
|
||
g_signal_handler_disconnect (execution_scenario,
|
||
data->subaction_done_sigid);
|
||
data->subaction_done_sigid = 0;
|
||
}
|
||
g_mutex_unlock (&data->sigid_lock);
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_on_sub_scenario (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res;
|
||
GstValidateScenario *execution_scenario = NULL;
|
||
|
||
res =
|
||
gst_validate_action_get_execution_scenario (action, &execution_scenario);
|
||
if (res != GST_VALIDATE_EXECUTE_ACTION_OK) {
|
||
GST_ERROR ("Action %" GST_PTR_FORMAT " could not get execution scenario",
|
||
action->structure);
|
||
|
||
gst_object_unref (execution_scenario);
|
||
return res;
|
||
}
|
||
|
||
GstStructure *structure;
|
||
GstValidateActionType *action_type;
|
||
REPORT_UNLESS (gst_structure_get (action->structure, "action",
|
||
GST_TYPE_STRUCTURE, &structure, NULL), err,
|
||
"Could not get `action` structure");
|
||
|
||
action_type = _find_action_type (gst_structure_get_name (structure));
|
||
REPORT_UNLESS (action_type, err, "Action type %s no found",
|
||
gst_structure_get_name (structure));
|
||
|
||
GstValidateAction *subaction =
|
||
gst_validate_create_subaction (execution_scenario, NULL,
|
||
action, structure, 0, 0);
|
||
|
||
/* FIXME Cleanly inject action type instead of stepping on the other
|
||
* scenario's feet */
|
||
SCENARIO_LOCK (execution_scenario);
|
||
execution_scenario->priv->actions =
|
||
g_list_prepend (execution_scenario->priv->actions, subaction);
|
||
SCENARIO_UNLOCK (execution_scenario);
|
||
|
||
ValidateActionForeignScenarioData *data =
|
||
g_atomic_rc_box_new0 (ValidateActionForeignScenarioData);
|
||
|
||
g_mutex_lock (&data->sigid_lock);
|
||
data->action = gst_validate_action_ref (action);
|
||
data->subaction_done_sigid =
|
||
g_signal_connect_data (execution_scenario, "action-done",
|
||
G_CALLBACK (gst_validate_foreign_subaction_done_cb),
|
||
g_atomic_rc_box_acquire (data),
|
||
(GClosureNotify) validate_action_foreign_scenario_data_unref, 0);
|
||
|
||
gst_validate_print_action (action, NULL);
|
||
res = subaction->priv->state =
|
||
gst_validate_execute_action (action_type, subaction);
|
||
|
||
if (res != GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING
|
||
&& res != GST_VALIDATE_EXECUTE_ACTION_ASYNC) {
|
||
|
||
if (data->subaction_done_sigid) {
|
||
g_signal_handler_disconnect (execution_scenario,
|
||
data->subaction_done_sigid);
|
||
data->subaction_done_sigid = 0;
|
||
}
|
||
|
||
gst_validate_action_set_done (subaction);
|
||
}
|
||
g_mutex_unlock (&data->sigid_lock);
|
||
validate_action_foreign_scenario_data_unref (data);
|
||
|
||
done:
|
||
return res;
|
||
|
||
err:
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
/**
|
||
* gst_validate_execute_action:
|
||
* @action_type: The #GstValidateActionType to execute
|
||
* @action: (transfer full): The #GstValidateAction to execute
|
||
*
|
||
* Executes @action
|
||
*/
|
||
GstValidateExecuteActionReturn
|
||
gst_validate_execute_action (GstValidateActionType * action_type,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res;
|
||
GstValidateScenario *scenario;
|
||
|
||
g_return_val_if_fail (g_strcmp0 (action_type->name, action->type) == 0,
|
||
GST_VALIDATE_EXECUTE_ACTION_ERROR);
|
||
|
||
scenario = gst_validate_action_get_scenario (action);
|
||
g_assert (scenario);
|
||
|
||
action->priv->context = g_main_context_ref (scenario->priv->context);
|
||
if (action_type->prepare) {
|
||
res = action_type->prepare (action);
|
||
if (res == GST_VALIDATE_EXECUTE_ACTION_DONE) {
|
||
gst_validate_print_action (action, NULL);
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
|
||
if (res != GST_VALIDATE_EXECUTE_ACTION_OK) {
|
||
GST_ERROR_OBJECT (scenario, "Action %" GST_PTR_FORMAT
|
||
" could not be prepared", action->structure);
|
||
|
||
gst_object_unref (scenario);
|
||
return res;
|
||
}
|
||
}
|
||
|
||
gst_validate_print_action (action, NULL);
|
||
|
||
action->priv->execution_time = gst_util_get_timestamp ();
|
||
action->priv->state = GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS;
|
||
action_type->priv->n_calls++;
|
||
res = action_type->execute (scenario, action);
|
||
gst_object_unref (scenario);
|
||
|
||
return res;
|
||
}
|
||
|
||
/* scenario can be NULL **only** if the action is a CONFIG action and
|
||
* add_to_lists is FALSE */
|
||
static GstValidateExecuteActionReturn
|
||
_fill_action (GstValidateScenario * scenario, GstValidateAction * action,
|
||
GstStructure * structure, gboolean add_to_lists)
|
||
{
|
||
gdouble playback_time;
|
||
gboolean is_config = FALSE;
|
||
GstValidateActionType *action_type;
|
||
GstValidateScenarioPrivate *priv = scenario ? scenario->priv : NULL;
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_NONE;
|
||
gboolean optional, needs_parsing = FALSE;
|
||
|
||
structure = gst_structure_copy (structure);
|
||
action->type = gst_structure_get_name (structure);
|
||
action_type = _find_action_type (action->type);
|
||
|
||
if (!action_type) {
|
||
GST_ERROR_OBJECT (scenario, "Action type %s no found",
|
||
gst_structure_get_name (structure));
|
||
|
||
gst_structure_free (structure);
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
}
|
||
|
||
if (gst_structure_get_double (structure, "playback-time", &playback_time) ||
|
||
gst_structure_get_double (structure, "playback_time", &playback_time)) {
|
||
action->playback_time = playback_time * GST_SECOND;
|
||
} else if (gst_structure_has_field_typed (structure, "playback-time",
|
||
G_TYPE_STRING)
|
||
|| gst_structure_has_field_typed (structure, "playback_time",
|
||
G_TYPE_STRING)) {
|
||
|
||
if (add_to_lists && priv) {
|
||
action->priv->needs_playback_parsing = TRUE;
|
||
needs_parsing = TRUE;
|
||
}
|
||
} else
|
||
GST_INFO_OBJECT (scenario,
|
||
"No playback time for action %" GST_PTR_FORMAT, structure);
|
||
|
||
if (!gst_validate_utils_get_clocktime (structure,
|
||
"timeout", &action->priv->timeout)) {
|
||
GST_INFO_OBJECT (scenario,
|
||
"No timeout time for action %" GST_PTR_FORMAT, structure);
|
||
}
|
||
|
||
action->structure = structure;
|
||
|
||
if (!(action->name = gst_structure_get_string (action->structure, "name")))
|
||
action->name = "";
|
||
|
||
if (!action->priv->main_structure)
|
||
action->priv->main_structure = gst_structure_copy (structure);
|
||
|
||
if (gst_structure_get_boolean (structure, "optional", &optional)) {
|
||
if ((action_type->flags & GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL) == 0) {
|
||
GST_ERROR_OBJECT (scenario, "Action type %s can't be optional",
|
||
gst_structure_get_name (structure));
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
}
|
||
action->priv->optional = optional;
|
||
}
|
||
|
||
if (IS_CONFIG_ACTION_TYPE (action_type->flags) ||
|
||
(gst_structure_get_boolean (action->structure, "as-config",
|
||
&is_config) && is_config == TRUE)) {
|
||
|
||
action_type->priv->n_calls++;
|
||
res = action_type->execute (scenario, action);
|
||
gst_validate_print_action (action, NULL);
|
||
|
||
return res;
|
||
}
|
||
|
||
if (!add_to_lists)
|
||
return res;
|
||
|
||
if (priv != NULL) {
|
||
GstValidateActionType *type = _find_action_type (action->type);
|
||
gboolean can_execute_on_addition =
|
||
type->flags & GST_VALIDATE_ACTION_TYPE_CAN_EXECUTE_ON_ADDITION
|
||
&& !GST_CLOCK_TIME_IS_VALID (action->playback_time)
|
||
&& !gst_structure_has_field (action->structure, "on-message");
|
||
|
||
if (needs_parsing)
|
||
can_execute_on_addition = FALSE;
|
||
|
||
if (can_execute_on_addition) {
|
||
GList *tmp;
|
||
|
||
for (tmp = priv->actions; tmp; tmp = tmp->next) {
|
||
GstValidateAction *act = (GstValidateAction *) tmp->data;
|
||
if (GST_CLOCK_TIME_IS_VALID (act->playback_time)) {
|
||
can_execute_on_addition = FALSE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
if (can_execute_on_addition) {
|
||
SCENARIO_LOCK (scenario);
|
||
priv->on_addition_actions = g_list_append (priv->on_addition_actions,
|
||
action);
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
} else {
|
||
priv->actions = g_list_append (priv->actions, action);
|
||
}
|
||
}
|
||
|
||
return res;
|
||
}
|
||
|
||
static gboolean
|
||
gst_validate_scenario_execute_next_or_restart_looping (GstValidateScenario *
|
||
scenario)
|
||
{
|
||
/* Recurse to the next action if it is possible
|
||
* to execute right away */
|
||
if (!scenario->priv->execute_on_idle) {
|
||
GST_DEBUG_OBJECT (scenario, "linking next action execution");
|
||
|
||
return execute_next_action (scenario);
|
||
} else {
|
||
_add_execute_actions_gsource (scenario);
|
||
GST_DEBUG_OBJECT (scenario, "Executing only on idle, waiting for"
|
||
" next dispatch");
|
||
}
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
|
||
/* This is the main action execution function
|
||
* it checks whether it is time to run the next action
|
||
* and if it is the case executes it.
|
||
*
|
||
* If the 'execute-on-idle' property is not TRUE,
|
||
* the function will recurse while the actions are run
|
||
* synchronously
|
||
*/
|
||
static gboolean
|
||
execute_next_action_full (GstValidateScenario * scenario, GstMessage * message)
|
||
{
|
||
gdouble rate = 1.0;
|
||
GstClockTime position = -1;
|
||
GstValidateAction *act = NULL;
|
||
GstValidateActionType *type;
|
||
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
|
||
if (priv->buffering) {
|
||
GST_DEBUG_OBJECT (scenario, "Buffering not executing any action");
|
||
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
|
||
if (priv->changing_state || priv->needs_async_done) {
|
||
GST_DEBUG_OBJECT (scenario, "Changing state, not executing any action");
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
|
||
if (scenario->priv->actions)
|
||
act = scenario->priv->actions->data;
|
||
|
||
if (!act) {
|
||
_check_scenario_is_done (scenario);
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
|
||
if (message && GST_MESSAGE_TYPE (message) == GST_MESSAGE_EOS
|
||
&& act->playback_time != GST_CLOCK_TIME_NONE) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, act,
|
||
SCENARIO_ACTION_ENDED_EARLY,
|
||
"Got EOS before action playback time %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (act->playback_time));
|
||
goto execute_action;
|
||
}
|
||
|
||
switch (act->priv->state) {
|
||
case GST_VALIDATE_EXECUTE_ACTION_NONE:
|
||
case GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING:
|
||
break;
|
||
case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS:
|
||
case GST_VALIDATE_EXECUTE_ACTION_OK:
|
||
return G_SOURCE_CONTINUE;
|
||
case GST_VALIDATE_EXECUTE_ACTION_ASYNC:
|
||
if (GST_CLOCK_TIME_IS_VALID (act->priv->timeout)) {
|
||
GstClockTime etime =
|
||
gst_util_get_timestamp () - act->priv->execution_time;
|
||
|
||
if (etime > act->priv->timeout) {
|
||
gchar *str = gst_structure_to_string (act->structure);
|
||
|
||
GST_VALIDATE_REPORT_ACTION (scenario, act,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Action %s timed out after: %" GST_TIME_FORMAT, str,
|
||
GST_TIME_ARGS (etime));
|
||
|
||
g_free (str);
|
||
}
|
||
}
|
||
GST_LOG_OBJECT (scenario, "Action %" GST_PTR_FORMAT " still running",
|
||
act->structure);
|
||
|
||
return G_SOURCE_CONTINUE;
|
||
default:
|
||
GST_ERROR_OBJECT (scenario, "State is %s(%d)",
|
||
gst_validate_action_return_get_name (act->priv->state),
|
||
act->priv->state);
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
if (message) {
|
||
if (!_check_message_type (scenario, act, message))
|
||
return G_SOURCE_CONTINUE;
|
||
} else if ((act && gst_structure_get_string (act->structure, "on-message") &&
|
||
!GST_CLOCK_TIME_IS_VALID (act->playback_time)) ||
|
||
(!_check_position (scenario, act, &position, &rate))) {
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
|
||
if (!_should_execute_action (scenario, act, position, rate)) {
|
||
_add_execute_actions_gsource (scenario);
|
||
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
|
||
execute_action:
|
||
type = _find_action_type (act->type);
|
||
|
||
GST_DEBUG_OBJECT (scenario, "Executing %" GST_PTR_FORMAT
|
||
" at %" GST_TIME_FORMAT, act->structure, GST_TIME_ARGS (position));
|
||
priv->seeked_in_pause = FALSE;
|
||
|
||
if (message)
|
||
gst_structure_remove_field (act->structure, "playback-time");
|
||
else
|
||
gst_structure_remove_field (act->structure, "on-message");
|
||
|
||
act->priv->state = gst_validate_execute_action (type, act);
|
||
switch (act->priv->state) {
|
||
case GST_VALIDATE_EXECUTE_ACTION_ASYNC:
|
||
GST_DEBUG_OBJECT (scenario, "Remove source, waiting for action"
|
||
" to be done.");
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
priv->execute_actions_source_id = 0;
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS:
|
||
return G_SOURCE_CONTINUE;
|
||
case GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING:
|
||
SCENARIO_LOCK (scenario);
|
||
priv->non_blocking_running_actions =
|
||
g_list_append (priv->non_blocking_running_actions, act);
|
||
priv->actions = g_list_remove (priv->actions, act);
|
||
SCENARIO_UNLOCK (scenario);
|
||
return gst_validate_scenario_execute_next_or_restart_looping (scenario);
|
||
default:
|
||
gst_validate_action_set_done (act);
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
execute_next_action (GstValidateScenario * scenario)
|
||
{
|
||
return execute_next_action_full (scenario, NULL);
|
||
}
|
||
|
||
static gboolean
|
||
stop_waiting (GstValidateAction * action)
|
||
{
|
||
GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
scenario->priv->wait_id = 0;
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
gst_validate_action_set_done (action);
|
||
_add_execute_actions_gsource (scenario);
|
||
gst_object_unref (scenario);
|
||
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
typedef struct
|
||
{
|
||
GstElement *target;
|
||
GstValidateAction *action;
|
||
guint sigid;
|
||
gboolean check_done;
|
||
gboolean check_property;
|
||
|
||
gchar *parent_name;
|
||
gchar *object_name;
|
||
gchar *property_name;
|
||
GMutex lock;
|
||
} WaitingSignalData;
|
||
|
||
static WaitingSignalData *
|
||
waiting_signal_data_new (GstElement * target, GstValidateAction * action,
|
||
gchar * parent_name, gchar * object_name, gchar * property_name)
|
||
{
|
||
WaitingSignalData *data = g_new0 (WaitingSignalData, 1);
|
||
|
||
data->parent_name = parent_name;
|
||
data->object_name = object_name;
|
||
data->property_name = property_name;
|
||
data->target = gst_object_ref (target);
|
||
data->action = gst_validate_action_ref (action);
|
||
|
||
return data;
|
||
}
|
||
|
||
static void
|
||
waiting_signal_data_free (WaitingSignalData * data)
|
||
{
|
||
GstValidateScenario *scenario =
|
||
gst_validate_action_get_scenario (data->action);
|
||
|
||
g_assert (scenario);
|
||
|
||
g_free (data->object_name);
|
||
g_free (data->parent_name);
|
||
g_free (data->property_name);
|
||
gst_object_unref (data->target);
|
||
gst_validate_action_unref (data->action);
|
||
g_free (data);
|
||
|
||
gst_object_unref (scenario);
|
||
}
|
||
|
||
static void
|
||
waiting_signal_data_disconnect (WaitingSignalData * data,
|
||
GstValidateScenario * scenario)
|
||
{
|
||
g_assert (scenario);
|
||
SCENARIO_LOCK (scenario);
|
||
g_signal_handler_disconnect (data->target,
|
||
data->sigid ? data->sigid : scenario->priv->signal_handler_id);
|
||
if (!data->sigid)
|
||
scenario->priv->signal_handler_id = 0;
|
||
data->sigid = 0;
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
}
|
||
|
||
static void
|
||
stop_waiting_signal_cb (WaitingSignalData * data, GstObject * prop_object,
|
||
GParamSpec * prop, GstObject * object)
|
||
{
|
||
GstStructure *check = NULL;
|
||
GstValidateAction *action = gst_validate_action_ref (data->action);
|
||
GstValidateScenario *scenario = NULL;
|
||
const gchar *property_name = NULL;
|
||
gboolean check_property = data->check_property;
|
||
GstObject *property_check_target = GST_OBJECT_CAST (data->target);
|
||
|
||
g_mutex_lock (&data->lock);
|
||
if (data->check_done) {
|
||
GST_INFO_OBJECT (data->action, "Check already done, ignoring signal");
|
||
g_mutex_unlock (&data->lock);
|
||
|
||
goto cleanup;
|
||
}
|
||
|
||
if (data->object_name) {
|
||
if (g_strcmp0 (data->object_name, GST_OBJECT_NAME (prop_object)) != 0) {
|
||
goto cleanup;
|
||
}
|
||
|
||
if (g_strcmp0 (data->property_name, prop->name) != 0) {
|
||
goto cleanup;
|
||
}
|
||
|
||
if (data->parent_name) {
|
||
GstObject *parent = gst_object_get_parent (prop_object);
|
||
|
||
if (parent && g_strcmp0 (data->parent_name, GST_OBJECT_NAME (parent))) {
|
||
goto cleanup;
|
||
}
|
||
|
||
gst_clear_object (&parent);
|
||
}
|
||
|
||
property_check_target = prop_object;
|
||
property_name = data->property_name;
|
||
check_property =
|
||
gst_structure_has_field (action->structure, "property-value");
|
||
|
||
} else {
|
||
property_name =
|
||
gst_structure_get_string (action->structure, "property-name");
|
||
}
|
||
|
||
scenario = gst_validate_action_get_scenario (data->action);
|
||
if (check_property &&
|
||
_check_property (scenario, action, property_check_target,
|
||
property_name,
|
||
gst_structure_get_value (action->structure, "property-value"),
|
||
FALSE) != GST_VALIDATE_EXECUTE_ACTION_OK) {
|
||
|
||
GST_INFO_OBJECT (scenario, "Property check failed, keep waiting");
|
||
|
||
goto cleanup;
|
||
}
|
||
data->check_done = TRUE;
|
||
|
||
waiting_signal_data_disconnect (data, scenario);
|
||
if (gst_structure_get (action->structure, "check", GST_TYPE_STRUCTURE,
|
||
&check, NULL)) {
|
||
GstValidateAction *subact =
|
||
gst_validate_create_subaction (scenario, NULL, action,
|
||
check, 0, 0);
|
||
GstValidateActionType *subact_type = _find_action_type (subact->type);
|
||
if (!(subact_type->flags & GST_VALIDATE_ACTION_TYPE_CHECK)) {
|
||
gst_validate_error_structure (action,
|
||
"`check` action %s is not marked as 'check'", subact->type);
|
||
}
|
||
|
||
gst_validate_execute_action (subact_type, subact);
|
||
gst_validate_action_unref (subact);
|
||
}
|
||
|
||
gst_validate_action_set_done (action);
|
||
_add_execute_actions_gsource (scenario);
|
||
|
||
cleanup:
|
||
gst_validate_action_unref (action);
|
||
gst_clear_object (&scenario);
|
||
g_mutex_unlock (&data->lock);
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_timed_wait (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
GstClockTime duration;
|
||
|
||
gdouble wait_multiplier = 1;
|
||
const gchar *str_wait_multiplier =
|
||
g_getenv ("GST_VALIDATE_SCENARIO_WAIT_MULTIPLIER");
|
||
|
||
if (str_wait_multiplier) {
|
||
errno = 0;
|
||
wait_multiplier = g_ascii_strtod (str_wait_multiplier, NULL);
|
||
|
||
if (errno) {
|
||
GST_ERROR ("Could not use the WAIT MULTIPLIER");
|
||
|
||
wait_multiplier = 1;
|
||
}
|
||
|
||
if (wait_multiplier == 0) {
|
||
GST_INFO_OBJECT (scenario, "I have been told not to wait...");
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
}
|
||
|
||
if (!gst_validate_action_get_clocktime (scenario, action,
|
||
"duration", &duration)) {
|
||
GST_DEBUG_OBJECT (scenario, "Duration could not be parsed");
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
}
|
||
|
||
duration *= wait_multiplier;
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
if (priv->execute_actions_source_id) {
|
||
g_source_remove (priv->execute_actions_source_id);
|
||
priv->execute_actions_source_id = 0;
|
||
}
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
priv->wait_id = g_timeout_add (duration / G_USEC_PER_SEC,
|
||
(GSourceFunc) stop_waiting, action);
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_wait_for_signal (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
gboolean non_blocking;
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
gchar *signal_name = g_strdup (gst_structure_get_string
|
||
(action->structure, "signal-name"));
|
||
const gchar *property_name = gst_structure_get_string
|
||
(action->structure, "property-name");
|
||
GList *targets = NULL;
|
||
GstElement *target;
|
||
WaitingSignalData *data;
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
const GValue *property_value =
|
||
gst_structure_get_value (action->structure, "property-value");
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
const gchar *deep_property_path =
|
||
gst_structure_get_string (action->structure, "deep-property-path");
|
||
gboolean checking_property = signal_name == NULL
|
||
&& deep_property_path == NULL;
|
||
gchar *parent_name = NULL, *object_name = NULL, *deep_property_name = NULL;
|
||
|
||
if (deep_property_path && *deep_property_path) {
|
||
gchar **elem_pad_name = g_strsplit (deep_property_path, ".", 2);
|
||
gchar **object_prop_name =
|
||
g_strsplit (elem_pad_name[1] ? elem_pad_name[1] : elem_pad_name[0],
|
||
"::",
|
||
-1);
|
||
|
||
REPORT_UNLESS (object_prop_name[1], done,
|
||
"Property specification %s is missing a `::propename` part",
|
||
deep_property_path);
|
||
|
||
deep_property_name = g_strdup (object_prop_name[1]);
|
||
object_name = g_strdup (object_prop_name[0]);
|
||
if (elem_pad_name[1]) {
|
||
parent_name = g_strdup (elem_pad_name[0]);
|
||
}
|
||
|
||
g_strfreev (elem_pad_name);
|
||
g_strfreev (object_prop_name);
|
||
|
||
target = gst_object_ref (pipeline);
|
||
signal_name = g_strdup ("deep-notify");
|
||
|
||
gst_validate_printf (action, "Waiting for 'deep-notify::%s'\n",
|
||
deep_property_name);
|
||
} else {
|
||
|
||
REPORT_UNLESS (signal_name || property_name, done,
|
||
"No signal-name or property-name given for wait action");
|
||
REPORT_UNLESS (!property_name || (property_name && property_value), done,
|
||
"`property-name` specified without a `property-value`");
|
||
|
||
targets = _find_elements_defined_in_action (scenario, action);
|
||
REPORT_UNLESS ((g_list_length (targets) == 1), done,
|
||
"Could not find target element.");
|
||
target = targets->data;
|
||
gst_validate_printf (action, "Waiting for '%s'\n",
|
||
signal_name ? signal_name : property_name);
|
||
}
|
||
|
||
|
||
data =
|
||
waiting_signal_data_new (target, action, parent_name, object_name,
|
||
deep_property_name);
|
||
|
||
if (checking_property) {
|
||
signal_name = g_strdup_printf ("notify::%s", property_name);
|
||
g_mutex_lock (&data->lock);
|
||
}
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
if (priv->execute_actions_source_id) {
|
||
g_source_remove (priv->execute_actions_source_id);
|
||
priv->execute_actions_source_id = 0;
|
||
}
|
||
priv->signal_handler_id = g_signal_connect_data (target, signal_name,
|
||
(GCallback) stop_waiting_signal_cb, data,
|
||
(GClosureNotify) waiting_signal_data_free, G_CONNECT_SWAPPED);
|
||
|
||
non_blocking =
|
||
gst_structure_get_boolean (action->structure, "non-blocking",
|
||
&non_blocking);
|
||
if (non_blocking) {
|
||
data->sigid = priv->signal_handler_id;
|
||
priv->signal_handler_id = 0;
|
||
}
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
res =
|
||
non_blocking ? GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING :
|
||
GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
if (checking_property) {
|
||
if (_check_property (scenario, action, data->target, property_name,
|
||
property_value, FALSE) == GST_VALIDATE_EXECUTE_ACTION_OK) {
|
||
data->check_done = TRUE;
|
||
waiting_signal_data_disconnect (data, scenario);
|
||
|
||
GST_ERROR ("Property check done, returning OK");
|
||
res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
g_mutex_unlock (&data->lock);
|
||
}
|
||
|
||
done:
|
||
g_free (signal_name);
|
||
g_list_free_full (targets, gst_object_unref);
|
||
gst_object_unref (pipeline);
|
||
|
||
return res;
|
||
}
|
||
|
||
static gboolean
|
||
_execute_wait_for_message (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
const gchar *message_type = gst_structure_get_string
|
||
(action->structure, "message-type");
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
gst_validate_printf (action, "Waiting for '%s' message\n", message_type);
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
if (priv->execute_actions_source_id) {
|
||
g_source_remove (priv->execute_actions_source_id);
|
||
priv->execute_actions_source_id = 0;
|
||
}
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
g_assert (!priv->wait_message_action);
|
||
priv->wait_message_action = gst_validate_action_ref (action);
|
||
gst_object_unref (pipeline);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
}
|
||
|
||
static void
|
||
sub_pipeline_done_cb (GstBus * bus, GstMessage * message, gpointer data)
|
||
{
|
||
GstState state;
|
||
|
||
gst_message_parse_request_state (message, &state);
|
||
|
||
if (!GST_IS_VALIDATE_SCENARIO (GST_MESSAGE_SRC (message))
|
||
|| state != GST_STATE_NULL) {
|
||
return;
|
||
}
|
||
|
||
gst_validate_action_set_done (data);
|
||
}
|
||
|
||
static gboolean
|
||
_execute_wait_for_sub_pipeline (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
const gchar *subpipeline_name =
|
||
gst_structure_get_string (action->structure, "subpipeline-done");
|
||
GstElement *pipeline = gst_validate_scenario_get_sub_pipeline (scenario,
|
||
subpipeline_name);
|
||
|
||
if (!pipeline) {
|
||
/* FIXME - ensure it actually ran at some point */
|
||
GST_INFO_OBJECT (scenario, "Could not find %s - considering done",
|
||
subpipeline_name);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
|
||
GstBus *bus = gst_element_get_bus (GST_ELEMENT (pipeline));
|
||
|
||
gst_bus_enable_sync_message_emission (bus);
|
||
|
||
g_signal_connect_data (bus, "sync-message::request-state",
|
||
(GCallback) sub_pipeline_done_cb, gst_validate_action_ref (action),
|
||
(GClosureNotify) gst_validate_action_unref, G_CONNECT_AFTER);
|
||
|
||
gst_clear_object (&bus);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
}
|
||
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_wait (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
gboolean on_clock = FALSE;
|
||
|
||
gst_structure_get_boolean (action->structure, "on-clock", &on_clock);
|
||
if (gst_structure_has_field (action->structure, "signal-name")) {
|
||
return _execute_wait_for_signal (scenario, action);
|
||
} else if (gst_structure_has_field (action->structure, "deep-property-path")) {
|
||
return _execute_wait_for_signal (scenario, action);
|
||
} else if (gst_structure_has_field (action->structure, "property-name")) {
|
||
return _execute_wait_for_signal (scenario, action);
|
||
} else if (gst_structure_has_field (action->structure, "message-type")) {
|
||
return _execute_wait_for_message (scenario, action);
|
||
} else if (on_clock) {
|
||
gst_test_clock_wait_for_next_pending_id (scenario->priv->clock, NULL);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
} else if (gst_structure_has_field_typed (action->structure,
|
||
"subpipeline-done", G_TYPE_STRING)) {
|
||
return _execute_wait_for_sub_pipeline (scenario, action);
|
||
} else {
|
||
return _execute_timed_wait (scenario, action);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
_execute_dot_pipeline (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
gchar *dotname;
|
||
gint details = GST_DEBUG_GRAPH_SHOW_ALL;
|
||
const gchar *name = gst_structure_get_string (action->structure, "name");
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
gst_structure_get_int (action->structure, "details", &details);
|
||
if (name)
|
||
dotname = g_strdup_printf ("validate.action.%s", name);
|
||
else
|
||
dotname = g_strdup ("validate.action.unnamed");
|
||
|
||
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline), details, dotname);
|
||
|
||
g_free (dotname);
|
||
gst_object_unref (pipeline);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GstElement *
|
||
_get_target_element (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
const gchar *name;
|
||
GstElement *target;
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
|
||
if (!pipeline) {
|
||
GST_ERROR_OBJECT (scenario, "No pipeline set anymore!");
|
||
|
||
return NULL;
|
||
}
|
||
|
||
name = gst_structure_get_string (action->structure, "target-element-name");
|
||
if (name == NULL) {
|
||
gst_object_unref (pipeline);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
if (g_strcmp0 (GST_OBJECT_NAME (pipeline), name) == 0) {
|
||
target = gst_object_ref (pipeline);
|
||
} else {
|
||
target = gst_bin_get_by_name (GST_BIN (pipeline), name);
|
||
}
|
||
|
||
if (target == NULL)
|
||
GST_ERROR ("Target element with given name (%s) not found", name);
|
||
gst_object_unref (pipeline);
|
||
|
||
return target;
|
||
}
|
||
|
||
/* _get_target_elements_by_klass_or_factory_name:
|
||
* @scenario: a #GstValidateScenario
|
||
* @action: a #GstValidateAction
|
||
*
|
||
* Returns all the elements in the pipeline whose GST_ELEMENT_METADATA_KLASS
|
||
* matches the 'target-element-klass' of @action and the factory name matches
|
||
* the 'target-element-factory-name'.
|
||
*
|
||
* Returns: (transfer full) (element-type GstElement): a list of #GstElement
|
||
*/
|
||
static GList *
|
||
_get_target_elements_by_klass_or_factory_name (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GList *result = NULL;
|
||
GstIterator *it;
|
||
const gchar *klass, *fname;
|
||
GValue v = G_VALUE_INIT, param = G_VALUE_INIT;
|
||
gboolean done = FALSE;
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
|
||
if (!pipeline) {
|
||
GST_ERROR_OBJECT (scenario, "No pipeline set anymore!");
|
||
|
||
return NULL;
|
||
}
|
||
|
||
klass = gst_structure_get_string (action->structure, "target-element-klass");
|
||
fname =
|
||
gst_structure_get_string (action->structure,
|
||
"target-element-factory-name");
|
||
if (!klass && !fname) {
|
||
gst_object_unref (pipeline);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
if (klass && gst_validate_element_has_klass (pipeline, klass))
|
||
result = g_list_prepend (result, gst_object_ref (pipeline));
|
||
|
||
if (fname && gst_element_get_factory (pipeline)
|
||
&& !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (pipeline)),
|
||
fname))
|
||
result = g_list_prepend (result, gst_object_ref (pipeline));
|
||
|
||
it = gst_bin_iterate_recurse (GST_BIN (pipeline));
|
||
|
||
g_value_init (¶m, G_TYPE_STRING);
|
||
g_value_set_string (¶m, klass);
|
||
|
||
while (!done) {
|
||
switch (gst_iterator_next (it, &v)) {
|
||
case GST_ITERATOR_OK:{
|
||
GstElement *child = g_value_get_object (&v);
|
||
|
||
if (g_list_find (result, child))
|
||
goto next;
|
||
|
||
if (klass && gst_validate_element_has_klass (child, klass)) {
|
||
result = g_list_prepend (result, gst_object_ref (child));
|
||
goto next;
|
||
}
|
||
|
||
if (fname && gst_element_get_factory (child)
|
||
&& !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (child)),
|
||
fname))
|
||
result = g_list_prepend (result, gst_object_ref (child));
|
||
next:
|
||
g_value_reset (&v);
|
||
}
|
||
break;
|
||
case GST_ITERATOR_RESYNC:
|
||
gst_iterator_resync (it);
|
||
break;
|
||
case GST_ITERATOR_ERROR:
|
||
case GST_ITERATOR_DONE:
|
||
done = TRUE;
|
||
}
|
||
}
|
||
|
||
g_value_reset (&v);
|
||
g_value_reset (¶m);
|
||
gst_iterator_free (it);
|
||
gst_object_unref (pipeline);
|
||
|
||
return result;
|
||
}
|
||
|
||
static GList *
|
||
_find_elements_defined_in_action (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstElement *target;
|
||
GList *targets = NULL;
|
||
|
||
/* set-property can be applied on either:
|
||
* - a single element having target-element-name as name
|
||
* - all the elements having target-element-klass as klass
|
||
*/
|
||
if (gst_structure_get_string (action->structure, "target-element-name")) {
|
||
target = _get_target_element (scenario, action);
|
||
if (target == NULL)
|
||
return FALSE;
|
||
|
||
targets = g_list_append (targets, target);
|
||
} else if (gst_structure_get_string (action->structure,
|
||
"target-element-klass") ||
|
||
gst_structure_get_string (action->structure,
|
||
"target-element-factory-name")) {
|
||
targets = _get_target_elements_by_klass_or_factory_name (scenario, action);
|
||
}
|
||
|
||
return targets;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_check_action_type_calls (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
const gchar *type;
|
||
GstValidateActionType *t;
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
gint n;
|
||
|
||
REPORT_UNLESS (gst_structure_get_int (action->structure, "n", &n),
|
||
done, "No `n`!");
|
||
REPORT_UNLESS ((type = gst_structure_get_string (action->structure, "type")),
|
||
done, "No `type`!");
|
||
REPORT_UNLESS ((t =
|
||
_find_action_type (type)), done, "Can't find `%s`!", type);
|
||
REPORT_UNLESS (t->priv->n_calls == n, done,
|
||
"%s called %d times instead of expected %d", type, t->priv->n_calls, n);
|
||
|
||
|
||
done:
|
||
return res;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_check_subaction_level (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
gint n;
|
||
|
||
REPORT_UNLESS (gst_structure_get_int (action->structure, "level", &n),
|
||
done, "No `n`!");
|
||
REPORT_UNLESS (gst_validate_action_get_level (action) == n, done,
|
||
"Expected subaction level %d, got %d", n,
|
||
gst_validate_action_get_level (action));
|
||
|
||
|
||
done:
|
||
return res;
|
||
}
|
||
|
||
static gboolean
|
||
set_env_var (const GstIdStr * fieldname, GValue * value,
|
||
GSubprocessLauncher * subproc_launcher)
|
||
{
|
||
g_subprocess_launcher_setenv (subproc_launcher, gst_id_str_as_str (fieldname),
|
||
g_value_get_string (value), TRUE);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_run_command (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
gchar **argv = NULL, *_stderr = NULL;
|
||
GError *error = NULL;
|
||
const GValue *env = NULL;
|
||
GSubprocess *subproc = NULL;
|
||
GSubprocessLauncher *subproc_launcher = NULL;
|
||
GstValidateExecuteActionReturn res =
|
||
GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
|
||
REPORT_UNLESS ((argv = gst_validate_utils_get_strv (action->structure,
|
||
"argv")), done,
|
||
"Couldn't find `argv` as array of strings in %" GST_PTR_FORMAT,
|
||
action->structure);
|
||
|
||
subproc_launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDERR_PIPE);
|
||
g_subprocess_launcher_unsetenv (subproc_launcher, "GST_VALIDATE_SCENARIO");
|
||
g_subprocess_launcher_unsetenv (subproc_launcher, "GST_VALIDATE_CONFIG");
|
||
|
||
env = gst_structure_get_value (action->structure, "env");
|
||
REPORT_UNLESS (!env || GST_VALUE_HOLDS_STRUCTURE (env), done,
|
||
"The `env` parameter should be a GstStructure, got %s",
|
||
G_VALUE_TYPE_NAME (env));
|
||
if (env) {
|
||
gst_structure_foreach_id_str (gst_value_get_structure (env),
|
||
(GstStructureForeachIdStrFunc) set_env_var, subproc_launcher);
|
||
}
|
||
|
||
REPORT_UNLESS (
|
||
(subproc =
|
||
g_subprocess_launcher_spawnv (subproc_launcher,
|
||
(const gchar * const *) argv, &error)), done,
|
||
"Couldn't start subprocess: %s", error->message);
|
||
|
||
REPORT_UNLESS (g_subprocess_communicate_utf8 (subproc, NULL, NULL, NULL,
|
||
&_stderr, &error), done, "Failed to run check: %s", error->message);
|
||
|
||
REPORT_UNLESS (g_subprocess_get_exit_status (subproc) == 0,
|
||
done, "Sub command failed. Stderr: %s", _stderr);
|
||
|
||
g_free (_stderr);
|
||
|
||
res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
|
||
done:
|
||
if (argv)
|
||
g_strfreev (argv);
|
||
g_clear_object (&subproc_launcher);
|
||
g_clear_object (&subproc);
|
||
|
||
return res;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_check_pad_caps (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
GList *elements = NULL;
|
||
GstPad *pad = NULL;
|
||
GstStructure *expected_struct = NULL;
|
||
GstCaps *expected = NULL, *current_caps = NULL;
|
||
const gchar *pad_name, *comparison_type =
|
||
gst_structure_get_string (action->structure, "comparision-mode");
|
||
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
REPORT_UNLESS (elements =
|
||
_find_elements_defined_in_action (scenario, action), done,
|
||
"Could not find any element from %" GST_PTR_FORMAT, action->structure);
|
||
|
||
REPORT_UNLESS (g_list_length (elements) == 1, done,
|
||
"More than one element found from %" GST_PTR_FORMAT, action->structure);
|
||
|
||
pad_name = gst_structure_get_string (action->structure, "pad");
|
||
REPORT_UNLESS (pad =
|
||
gst_element_get_static_pad (elements->data, pad_name), done,
|
||
"Could not find pad %s in %" GST_PTR_FORMAT, pad_name, elements->data);
|
||
|
||
current_caps = gst_pad_get_current_caps (pad);
|
||
if (gst_structure_get (action->structure, "expected-caps", GST_TYPE_STRUCTURE,
|
||
&expected_struct, NULL))
|
||
expected = gst_caps_new_full (gst_structure_copy (expected_struct), NULL);
|
||
else
|
||
gst_structure_get (action->structure, "expected-caps", GST_TYPE_CAPS,
|
||
&expected, NULL);
|
||
|
||
if (!comparison_type || !g_strcmp0 (comparison_type, "intersect")) {
|
||
REPORT_UNLESS (expected, done, "Can't intersect with NULL expected caps");
|
||
REPORT_UNLESS (gst_caps_can_intersect (expected, current_caps), done,
|
||
"Caps can't intesect. Expected: \n - %" GST_PTR_FORMAT "\nGot:\n - %"
|
||
GST_PTR_FORMAT, expected, current_caps);
|
||
} else if (!g_strcmp0 (comparison_type, "equal")) {
|
||
REPORT_UNLESS ((expected == NULL && current_caps == NULL)
|
||
|| gst_caps_is_equal (expected, current_caps), done,
|
||
"Caps do not match. Expected: %" GST_PTR_FORMAT " got %" GST_PTR_FORMAT,
|
||
expected, current_caps);
|
||
} else {
|
||
REPORT_UNLESS (FALSE, done, "Invalid caps `comparision-type`: '%s'",
|
||
comparison_type);
|
||
}
|
||
|
||
done:
|
||
g_clear_object (&pipeline);
|
||
g_clear_object (&pad);
|
||
g_list_free_full (elements, gst_object_unref);
|
||
gst_clear_structure (&expected_struct);
|
||
gst_clear_caps (¤t_caps);
|
||
gst_clear_caps (&expected);
|
||
|
||
return res;
|
||
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_check_position (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstClockTime expected_pos, pos;
|
||
|
||
if (!gst_validate_action_get_clocktime (scenario, action,
|
||
"expected-position", &expected_pos)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not retrieve expected position in: %" GST_PTR_FORMAT,
|
||
action->structure);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
if (!_get_position (scenario, NULL, &pos)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "Could not get pipeline position");
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
if (pos != expected_pos) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Pipeline position doesn't match expectations"
|
||
" got %" GST_TIME_FORMAT " instead of %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (pos), GST_TIME_ARGS (expected_pos));
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_set_or_check_property (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GList *targets, *l;
|
||
const gchar *property;
|
||
const GValue *property_value;
|
||
gboolean ret = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
gboolean check = gst_structure_has_name (action->structure, "check-property");
|
||
|
||
targets = _find_elements_defined_in_action (scenario, action);
|
||
if (!targets) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"No element found for action: %" GST_PTR_FORMAT, action->structure);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
property = gst_structure_get_string (action->structure, "property-name");
|
||
property_value = gst_structure_get_value (action->structure,
|
||
"property-value");
|
||
|
||
for (l = targets; l != NULL; l = g_list_next (l)) {
|
||
if (!check) {
|
||
GstValidateActionReturn tmpres;
|
||
|
||
tmpres =
|
||
gst_validate_object_set_property (GST_VALIDATE_REPORTER (scenario),
|
||
G_OBJECT (l->data), property, property_value, action->priv->optional);
|
||
|
||
if (!tmpres)
|
||
ret = tmpres;
|
||
} else {
|
||
ret =
|
||
_check_property (scenario, action, l->data, property, property_value,
|
||
TRUE);
|
||
}
|
||
}
|
||
|
||
g_list_free_full (targets, gst_object_unref);
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
_execute_set_debug_threshold (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
gchar *str = NULL;
|
||
gboolean reset = TRUE;
|
||
const gchar *threshold_str;
|
||
|
||
threshold_str =
|
||
gst_structure_get_string (action->structure, "debug-threshold");
|
||
if (threshold_str == NULL) {
|
||
gint threshold;
|
||
|
||
if (gst_structure_get_int (action->structure, "debug-threshold",
|
||
&threshold))
|
||
threshold_str = str = g_strdup_printf ("%i", threshold);
|
||
else
|
||
return FALSE;
|
||
}
|
||
|
||
gst_structure_get_boolean (action->structure, "reset", &reset);
|
||
|
||
gst_debug_set_threshold_from_string (threshold_str, reset);
|
||
|
||
g_free (str);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_emit_signal (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
GstElement *target;
|
||
guint n_params = 0;
|
||
GSignalQuery query = { 0, };
|
||
GValue *values = NULL, lparams = { 0, };
|
||
const GValue *params;
|
||
|
||
REPORT_UNLESS ((target =
|
||
_get_target_element (scenario, action)), out, "No element found");
|
||
|
||
query.signal_name =
|
||
gst_structure_get_string (action->structure, "signal-name");
|
||
query.signal_id = g_signal_lookup (query.signal_name, G_OBJECT_TYPE (target));
|
||
REPORT_UNLESS (query.signal_id != 0, out, "Invalid signal `%s::%s`",
|
||
G_OBJECT_TYPE_NAME (target), query.signal_name);
|
||
|
||
g_signal_query (query.signal_id, &query);
|
||
|
||
params = gst_structure_get_value (action->structure, "params");
|
||
if (params) {
|
||
if (G_VALUE_HOLDS_STRING (params)) {
|
||
g_value_init (&lparams, GST_TYPE_ARRAY);
|
||
|
||
REPORT_UNLESS (gst_value_deserialize (&lparams,
|
||
g_value_get_string (params)), out,
|
||
"\"params\" argument should be a value array or a string deserializable"
|
||
" as value array, got string %s", g_value_get_string (params)
|
||
);
|
||
params = &lparams;
|
||
} else {
|
||
REPORT_UNLESS (GST_VALUE_HOLDS_ARRAY (params), out,
|
||
"\"params\" argument should be a value array, got %s",
|
||
G_VALUE_TYPE_NAME (params));
|
||
}
|
||
n_params = gst_value_array_get_size (params);
|
||
}
|
||
REPORT_UNLESS (query.n_params == (n_params), out,
|
||
"Expected %d `params` got %d", query.n_params, n_params);
|
||
values = g_malloc0 ((n_params + 2) * sizeof (GValue));
|
||
g_value_init (&values[0], G_OBJECT_TYPE (target));
|
||
g_value_take_object (&values[0], target);
|
||
for (gint i = 1; i < n_params + 1; i++) {
|
||
const GValue *param = gst_value_array_get_value (params, i - 1);
|
||
g_value_init (&values[i], query.param_types[i - 1]);
|
||
|
||
if (query.param_types[i - 1] == G_TYPE_BYTES
|
||
&& G_VALUE_TYPE (param) == G_TYPE_STRING) {
|
||
const gchar *s = g_value_get_string (param);
|
||
g_value_take_boxed (&values[i], g_bytes_new (s, strlen (s)));
|
||
} else {
|
||
REPORT_UNLESS (g_value_transform (param, &values[i]), out,
|
||
"Could not transform param %d from %s to %s", i - 1,
|
||
G_VALUE_TYPE_NAME (param), G_VALUE_TYPE_NAME (&values[i]));
|
||
}
|
||
}
|
||
|
||
g_signal_emitv (values, query.signal_id, 0, NULL);
|
||
|
||
for (gint i = 0; i < n_params + 1; i++)
|
||
g_value_reset (&values[i]);
|
||
|
||
if (G_VALUE_TYPE (&lparams))
|
||
g_value_reset (&lparams);
|
||
|
||
out:
|
||
return res;
|
||
|
||
}
|
||
|
||
typedef struct _ChainWrapperFunctionData ChainWrapperFunctionData;
|
||
typedef GstFlowReturn (*ChainWrapperFunction) (GstPad * pad, GstObject * parent,
|
||
GstBuffer * buffer, ChainWrapperFunctionData * data);
|
||
|
||
struct _ChainWrapperFunctionData
|
||
{
|
||
GstPadChainFunction wrapped_chain_func;
|
||
gpointer wrapped_chain_data;
|
||
GDestroyNotify wrapped_chain_notify;
|
||
ChainWrapperFunction wrapper_function;
|
||
gpointer wrapper_function_user_data;
|
||
|
||
GMutex actions_lock;
|
||
GList *actions;
|
||
|
||
};
|
||
|
||
static void
|
||
chain_wrapper_function_free (ChainWrapperFunctionData * data)
|
||
{
|
||
g_list_free_full (data->actions, (GDestroyNotify) gst_validate_action_unref);
|
||
g_mutex_clear (&data->actions_lock);
|
||
}
|
||
|
||
static GstFlowReturn
|
||
_pad_chain_wrapper (GstPad * pad, GstObject * parent, GstBuffer * buffer)
|
||
{
|
||
ChainWrapperFunctionData *data =
|
||
g_object_get_qdata (G_OBJECT (pad), chain_qdata);
|
||
|
||
return data->wrapper_function (pad, parent, buffer,
|
||
g_object_get_qdata (G_OBJECT (pad), chain_qdata));
|
||
}
|
||
|
||
static void
|
||
wrap_pad_chain_function (GstPad * pad, ChainWrapperFunction new_function,
|
||
GstValidateAction * action)
|
||
{
|
||
ChainWrapperFunctionData *data =
|
||
g_object_get_qdata (G_OBJECT (pad), chain_qdata);
|
||
|
||
if (data) {
|
||
g_mutex_lock (&data->actions_lock);
|
||
data->actions = g_list_append (data->actions, action);
|
||
g_mutex_unlock (&data->actions_lock);
|
||
|
||
return;
|
||
}
|
||
|
||
data = g_new0 (ChainWrapperFunctionData, 1);
|
||
data->actions = g_list_append (data->actions, action);
|
||
|
||
g_object_set_qdata_full (G_OBJECT (pad), chain_qdata, data,
|
||
(GDestroyNotify) chain_wrapper_function_free);
|
||
|
||
data->wrapped_chain_func = pad->chainfunc;
|
||
data->wrapper_function = new_function;
|
||
pad->chainfunc = _pad_chain_wrapper;
|
||
}
|
||
|
||
static GstFlowReturn
|
||
appsrc_push_chain_wrapper (GstPad * pad, GstObject * parent,
|
||
GstBuffer * buffer, ChainWrapperFunctionData * data)
|
||
{
|
||
GstValidateAction *action;
|
||
GstValidateScenario *scenario;
|
||
GstFlowReturn ret;
|
||
|
||
g_mutex_lock (&data->actions_lock);
|
||
if (data->actions) {
|
||
action = data->actions->data;
|
||
data->actions = g_list_remove (data->actions, action);
|
||
g_mutex_unlock (&data->actions_lock);
|
||
|
||
scenario = gst_validate_action_get_scenario (action);
|
||
} else {
|
||
g_mutex_unlock (&data->actions_lock);
|
||
|
||
return data->wrapped_chain_func (pad, parent, buffer);
|
||
}
|
||
|
||
GST_VALIDATE_SCENARIO_EOS_HANDLING_LOCK (scenario);
|
||
ret = data->wrapped_chain_func (pad, parent, buffer);
|
||
gst_validate_action_set_done (action);
|
||
gst_validate_action_unref (action);
|
||
GST_VALIDATE_SCENARIO_EOS_HANDLING_UNLOCK (scenario);
|
||
g_object_unref (scenario);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
structure_get_uint64_permissive (const GstStructure * structure,
|
||
const gchar * fieldname, guint64 * dest)
|
||
{
|
||
const GValue *original;
|
||
GValue transformed = G_VALUE_INIT;
|
||
|
||
original = gst_structure_get_value (structure, fieldname);
|
||
if (!original)
|
||
return FALSE;
|
||
|
||
g_value_init (&transformed, G_TYPE_UINT64);
|
||
if (!g_value_transform (original, &transformed))
|
||
return FALSE;
|
||
|
||
*dest = g_value_get_uint64 (&transformed);
|
||
g_value_unset (&transformed);
|
||
return TRUE;
|
||
}
|
||
|
||
typedef enum
|
||
{
|
||
APPSRC_PUSH_FILL_MODE_NOTHING,
|
||
APPSRC_PUSH_FILL_MODE_COUNTER,
|
||
APPSRC_PUSH_FILL_MODE_ZERO,
|
||
APPSRC_PUSH_FILL_MODE_FILE,
|
||
} AppSrcPushFillMode;
|
||
|
||
static AppSrcPushFillMode
|
||
appsrc_fill_mode_from_action (GstValidateAction * action)
|
||
{
|
||
const gchar *mode = gst_structure_get_string (action->structure, "fill-mode");
|
||
if (!mode)
|
||
return APPSRC_PUSH_FILL_MODE_FILE;
|
||
|
||
if (!g_strcmp0 (mode, "nothing"))
|
||
return APPSRC_PUSH_FILL_MODE_NOTHING;
|
||
else if (!g_strcmp0 (mode, "zero"))
|
||
return APPSRC_PUSH_FILL_MODE_ZERO;
|
||
else if (!g_strcmp0 (mode, "file"))
|
||
return APPSRC_PUSH_FILL_MODE_FILE;
|
||
else if (!g_strcmp0 (mode, "counter"))
|
||
return APPSRC_PUSH_FILL_MODE_COUNTER;
|
||
else
|
||
gst_validate_error_structure (action, "Invalid value for 'fill-mode': '%s'",
|
||
mode);
|
||
|
||
return APPSRC_PUSH_FILL_MODE_FILE;
|
||
}
|
||
|
||
static gint
|
||
_execute_appsrc_push (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstElement *target = NULL;
|
||
gchar *file_name = NULL;
|
||
gchar *data = NULL;
|
||
GError *error = NULL;
|
||
GstBuffer *buffer;
|
||
guint64 offset = 0;
|
||
guint64 size = 0, read;
|
||
gint push_sample_ret;
|
||
gboolean wait;
|
||
GFileInfo *finfo = NULL;
|
||
GFile *f = NULL;
|
||
GstPad *appsrc_pad = NULL;
|
||
GstPad *peer_pad = NULL;
|
||
GInputStream *stream = NULL;
|
||
GstValidateExecuteActionReturn res;
|
||
GstSegment segment;
|
||
GstCaps *caps = NULL;
|
||
GstSample *sample;
|
||
GstElement *pipeline = NULL;
|
||
static guint64 counter = 0;
|
||
|
||
/* We will only wait for the the buffer to be pushed if we are in a state
|
||
* that allows flow of buffers (>=PAUSED). Otherwise the buffer will just
|
||
* be enqueued. */
|
||
wait = scenario->priv->target_state >= GST_STATE_PAUSED;
|
||
|
||
target = _get_target_element (scenario, action);
|
||
REPORT_UNLESS (target, err, "No element found.");
|
||
|
||
const gchar *from_appsink =
|
||
gst_structure_get_string (action->structure, "from-appsink");
|
||
if (from_appsink) {
|
||
gchar **pipeline_elements = g_strsplit (from_appsink, "/", 2);
|
||
|
||
if (pipeline_elements[1]) {
|
||
pipeline =
|
||
gst_validate_scenario_get_sub_pipeline (scenario,
|
||
pipeline_elements[0]);
|
||
|
||
REPORT_UNLESS (pipeline, err, "Could not find subpipeline `%s`",
|
||
pipeline_elements[0]);
|
||
} else {
|
||
pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
}
|
||
GstElement *appsink =
|
||
gst_bin_get_by_name (GST_BIN (pipeline), pipeline_elements[1]);
|
||
REPORT_UNLESS (appsink, err, "Couldn't find appsink %s in %" GST_PTR_FORMAT,
|
||
pipeline_elements[1], pipeline);
|
||
|
||
g_signal_emit_by_name (appsink, "pull-sample", &sample, NULL);
|
||
|
||
g_strfreev (pipeline_elements);
|
||
goto push_sample;
|
||
}
|
||
|
||
structure_get_uint64_permissive (action->structure, "offset", &offset);
|
||
structure_get_uint64_permissive (action->structure, "size", &size);
|
||
|
||
AppSrcPushFillMode fill_mode = appsrc_fill_mode_from_action (action);
|
||
|
||
if (fill_mode == APPSRC_PUSH_FILL_MODE_FILE) {
|
||
file_name =
|
||
g_strdup (gst_structure_get_string (action->structure, "file-name"));
|
||
REPORT_UNLESS (file_name, err, "Missing file-name property.");
|
||
f = g_file_new_for_path (file_name);
|
||
stream = G_INPUT_STREAM (g_file_read (f, NULL, &error));
|
||
REPORT_UNLESS (!error, err, "Could not open file for action. Error: %s",
|
||
error->message);
|
||
|
||
if (offset > 0) {
|
||
read = g_input_stream_skip (stream, offset, NULL, &error);
|
||
REPORT_UNLESS (!error, err, "Could not skip to offset. Error: %s",
|
||
error->message);
|
||
REPORT_UNLESS (read == offset, err,
|
||
"Could not skip to offset, only skipped: %" G_GUINT64_FORMAT, read);
|
||
}
|
||
|
||
if (size <= 0) {
|
||
finfo =
|
||
g_file_query_info (f, G_FILE_ATTRIBUTE_STANDARD_SIZE,
|
||
G_FILE_QUERY_INFO_NONE, NULL, &error);
|
||
|
||
REPORT_UNLESS (!error, err, "Could not query file size. Error: %s",
|
||
error->message);
|
||
size = g_file_info_get_size (finfo);
|
||
}
|
||
|
||
data = g_malloc (size);
|
||
read = g_input_stream_read (stream, data, size, NULL, &error);
|
||
REPORT_UNLESS (!error, err, "Could not read input file. Error: %s",
|
||
error->message);
|
||
REPORT_UNLESS (read == size, err,
|
||
"Could read enough data, only read: %" G_GUINT64_FORMAT, read);
|
||
} else {
|
||
if (size == 0) {
|
||
size = 1000;
|
||
}
|
||
|
||
switch (fill_mode) {
|
||
case APPSRC_PUSH_FILL_MODE_NOTHING:
|
||
data = g_malloc (size);
|
||
break;
|
||
case APPSRC_PUSH_FILL_MODE_ZERO:
|
||
data = g_malloc0 (size);
|
||
break;
|
||
case APPSRC_PUSH_FILL_MODE_COUNTER:
|
||
if (size < sizeof (guint64)) {
|
||
gst_validate_error_structure (action,
|
||
"Can't fill with counter size > %" G_GSIZE_FORMAT
|
||
" required", sizeof (guint64));
|
||
}
|
||
|
||
data = g_malloc (size);
|
||
for (gsize i = 0; i + 8 <= size; i += 8) {
|
||
GST_WRITE_UINT64_LE (&data[i], counter++);
|
||
}
|
||
break;
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
}
|
||
|
||
buffer = gst_buffer_new_wrapped (data, size);
|
||
data = NULL;
|
||
gst_validate_action_get_clocktime (scenario,
|
||
action, "pts", &GST_BUFFER_PTS (buffer)
|
||
);
|
||
gst_validate_action_get_clocktime (scenario,
|
||
action, "dts", &GST_BUFFER_DTS (buffer)
|
||
);
|
||
gst_validate_action_get_clocktime (scenario,
|
||
action, "duration", &GST_BUFFER_DURATION (buffer)
|
||
);
|
||
|
||
{
|
||
const GValue *caps_value;
|
||
caps_value = gst_structure_get_value (action->structure, "caps");
|
||
if (caps_value) {
|
||
if (G_VALUE_HOLDS_STRING (caps_value)) {
|
||
caps = gst_caps_from_string (g_value_get_string (caps_value));
|
||
REPORT_UNLESS (caps, err, "Invalid caps string: %s",
|
||
g_value_get_string (caps_value));
|
||
} else {
|
||
caps = gst_caps_copy (gst_value_get_caps (caps_value));
|
||
}
|
||
|
||
REPORT_UNLESS (caps, err, "Could not get caps value");
|
||
}
|
||
}
|
||
|
||
sample = gst_sample_new (buffer, caps, NULL, NULL);
|
||
gst_clear_caps (&caps);
|
||
gst_buffer_unref (buffer);
|
||
if (gst_structure_has_field (action->structure, "segment")) {
|
||
GstFormat format;
|
||
GstStructure *segment_struct;
|
||
GstClockTime tmp;
|
||
|
||
REPORT_UNLESS (gst_structure_get (action->structure, "segment",
|
||
GST_TYPE_STRUCTURE, &segment_struct, NULL), err,
|
||
"Segment field not in right format (expected GstStructure).");
|
||
|
||
if (!gst_structure_get (segment_struct, "format", GST_TYPE_FORMAT, &format,
|
||
NULL))
|
||
g_object_get (target, "format", &format, NULL);
|
||
|
||
gst_segment_init (&segment, format);
|
||
if (gst_validate_utils_get_clocktime (segment_struct, "base", &tmp))
|
||
segment.base = tmp;
|
||
if (gst_validate_utils_get_clocktime (segment_struct, "offset", &tmp))
|
||
segment.offset = tmp;
|
||
if (gst_validate_utils_get_clocktime (segment_struct, "time", &tmp))
|
||
segment.time = tmp;
|
||
if (gst_validate_utils_get_clocktime (segment_struct, "position", &tmp))
|
||
segment.position = tmp;
|
||
if (gst_validate_utils_get_clocktime (segment_struct, "duration", &tmp))
|
||
segment.duration = tmp;
|
||
if (gst_validate_utils_get_clocktime (segment_struct, "start", &tmp))
|
||
segment.start = tmp;
|
||
if (gst_validate_utils_get_clocktime (segment_struct, "stop", &tmp))
|
||
segment.stop = tmp;
|
||
gst_structure_get_double (segment_struct, "rate", &segment.rate);
|
||
|
||
gst_structure_free (segment_struct);
|
||
|
||
gst_sample_set_segment (sample, &segment);
|
||
}
|
||
|
||
push_sample:
|
||
/* We temporarily override the peer pad chain function to finish the action
|
||
* once the buffer chain actually ends. */
|
||
appsrc_pad = gst_element_get_static_pad (target, "src");
|
||
peer_pad = gst_pad_get_peer (appsrc_pad);
|
||
REPORT_UNLESS (peer_pad, err, "Action failed, pad not linked");
|
||
wrap_pad_chain_function (peer_pad, appsrc_push_chain_wrapper, action);
|
||
|
||
/* Keep the action alive until set done is called. */
|
||
gst_validate_action_ref (action);
|
||
|
||
g_signal_emit_by_name (target, "push-sample", sample, &push_sample_ret);
|
||
gst_sample_unref (sample);
|
||
REPORT_UNLESS (push_sample_ret == GST_FLOW_OK, err,
|
||
"push-buffer signal failed in action.");
|
||
|
||
if (wait) {
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
} else {
|
||
gst_validate_printf (NULL,
|
||
"Pipeline is not ready to push buffers, interlacing appsrc-push action...\n");
|
||
res = GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING;
|
||
}
|
||
done:
|
||
gst_clear_object (&target);
|
||
gst_clear_object (&appsrc_pad);
|
||
gst_clear_object (&peer_pad);
|
||
g_clear_pointer (&file_name, g_free);
|
||
g_clear_pointer (&data, g_free);
|
||
g_clear_error (&error);
|
||
g_clear_object (&f);
|
||
g_clear_object (&finfo);
|
||
g_clear_object (&stream);
|
||
g_clear_object (&pipeline);
|
||
|
||
return res;
|
||
|
||
err:
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
static gint
|
||
_execute_appsrc_eos (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
GstElement *target;
|
||
gint eos_ret;
|
||
|
||
target = _get_target_element (scenario, action);
|
||
if (target == NULL) {
|
||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "No element found for action: %s",
|
||
structure_string);
|
||
g_free (structure_string);
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
g_signal_emit_by_name (target, "end-of-stream", &eos_ret);
|
||
if (eos_ret != GST_FLOW_OK) {
|
||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Failed to emit end-of-stream signal for action: %s", structure_string);
|
||
g_free (structure_string);
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
gst_object_unref (target);
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
|
||
static gint
|
||
_execute_flush (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
GstElement *target;
|
||
GstEvent *event;
|
||
gboolean ret;
|
||
gboolean reset_time = TRUE;
|
||
|
||
target = _get_target_element (scenario, action);
|
||
if (target == NULL) {
|
||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "No element found for action: %s",
|
||
structure_string);
|
||
g_free (structure_string);
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
gst_structure_get_boolean (action->structure, "reset-time", &reset_time);
|
||
|
||
// FLUSH_START and FLUSH_STOP are "fire-and-forget" events.
|
||
//
|
||
// In the case of a pipeline where the most downstream element has unlinked srcpads
|
||
// (as is the case for many autoplugging elements before preroll), the event will
|
||
// "fail" due to "not-linked" but will have still correctly flushed all the existing
|
||
// elements and pads.
|
||
//
|
||
// For this reason, we cannot consider a test failed if a FLUSH_START or FLUSH_STOP
|
||
// "fails". However, since those "failures" reflect edge cases, we still want to log
|
||
// them as they can become useful during debugging.
|
||
//
|
||
// Source: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7064#note_2460206
|
||
|
||
event = gst_event_new_flush_start ();
|
||
ret = gst_element_send_event (target, event);
|
||
GST_DEBUG_OBJECT (scenario, "Sending FLUSH_START event returned %s.",
|
||
ret ? "TRUE" : "FALSE");
|
||
|
||
event = gst_event_new_flush_stop (reset_time);
|
||
ret = gst_element_send_event (target, event);
|
||
GST_DEBUG_OBJECT (scenario, "Sending FLUSH_STOP event returned %s.",
|
||
ret ? "TRUE" : "FALSE");
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_disable_plugin (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstPlugin *plugin;
|
||
const gchar *plugin_name;
|
||
|
||
plugin_name = gst_structure_get_string (action->structure, "plugin-name");
|
||
|
||
plugin = gst_registry_find_plugin (gst_registry_get (), plugin_name);
|
||
|
||
if (plugin == NULL) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "Could not find plugin to disable: %s",
|
||
plugin_name);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
gst_validate_printf (action, "Disabling plugin \"%s\"\n", plugin_name);
|
||
gst_registry_remove_plugin (gst_registry_get (), plugin);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
|
||
static gboolean
|
||
gst_validate_action_setup_repeat (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
gchar *repeat_expr;
|
||
gchar *error = NULL;
|
||
gint repeat, position, i;
|
||
|
||
if (!gst_structure_has_field (action->structure, "repeat"))
|
||
return TRUE;
|
||
|
||
if (gst_structure_get_int (action->structure, "repeat", &repeat))
|
||
goto done;
|
||
|
||
if (gst_structure_get_double (action->structure, "repeat",
|
||
(gdouble *) & repeat))
|
||
goto done;
|
||
|
||
repeat_expr = gst_validate_replace_variables_in_string (action,
|
||
scenario->priv->vars, gst_structure_get_string (action->structure,
|
||
"repeat"), GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_ALL);
|
||
if (!repeat_expr) {
|
||
gst_validate_error_structure (action, "Invalid value for 'repeat'");
|
||
return FALSE;
|
||
}
|
||
|
||
repeat = gst_validate_utils_parse_expression (repeat_expr, _set_variable_func,
|
||
scenario, &error);
|
||
if (error) {
|
||
gst_validate_error_structure (action, "Invalid value for 'repeat': %s",
|
||
error);
|
||
g_free (error);
|
||
return FALSE;
|
||
}
|
||
g_free (repeat_expr);
|
||
|
||
done:
|
||
gst_structure_remove_field (action->structure, "repeat");
|
||
gst_structure_remove_field (action->priv->main_structure, "repeat");
|
||
|
||
action->repeat = 0;
|
||
GST_VALIDATE_ACTION_N_REPEATS (action) = repeat;
|
||
|
||
position = g_list_index (scenario->priv->actions, action);
|
||
g_assert (position >= 0);
|
||
for (i = 1; i < repeat; i++) {
|
||
GstValidateAction *copy = _action_copy (action);
|
||
|
||
copy->repeat = i;
|
||
scenario->priv->actions =
|
||
g_list_insert (scenario->priv->actions, copy, position + i);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
gst_validate_action_default_prepare_func (GstValidateAction * action)
|
||
{
|
||
gint i;
|
||
GstClockTime tmp;
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
GstValidateActionType *type = gst_validate_get_action_type (action->type);
|
||
GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
|
||
|
||
_update_well_known_vars (scenario);
|
||
if (!gst_validate_action_setup_repeat (scenario, action))
|
||
goto err;
|
||
|
||
if (GST_VALIDATE_ACTION_N_REPEATS (action)) {
|
||
if (action->priv->it_value.g_type != 0) {
|
||
gst_structure_set_value (scenario->priv->vars,
|
||
GST_VALIDATE_ACTION_RANGE_NAME (action), &action->priv->it_value);
|
||
} else {
|
||
gst_structure_set (scenario->priv->vars,
|
||
GST_VALIDATE_ACTION_RANGE_NAME (action) ?
|
||
GST_VALIDATE_ACTION_RANGE_NAME (action) : "repeat", G_TYPE_INT,
|
||
action->repeat, NULL);
|
||
}
|
||
}
|
||
gst_validate_structure_resolve_variables (action, action->structure,
|
||
scenario->priv->vars, GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_ALL);
|
||
for (i = 0; type->parameters[i].name; i++) {
|
||
if (type->parameters[i].types
|
||
&& g_str_has_suffix (type->parameters[i].types, "(GstClockTime)"))
|
||
gst_validate_action_get_clocktime (scenario, action,
|
||
type->parameters[i].name, &tmp);
|
||
}
|
||
|
||
|
||
done:
|
||
gst_clear_mini_object ((GstMiniObject **) & type);
|
||
if (scenario)
|
||
gst_object_unref (scenario);
|
||
|
||
return res;
|
||
err:
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR;
|
||
goto done;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
gst_validate_set_property_prepare_func (GstValidateAction * action)
|
||
{
|
||
action->priv->optional = gst_structure_has_field_typed (action->structure,
|
||
"on-all-instances", G_TYPE_BOOLEAN);
|
||
|
||
return gst_validate_action_default_prepare_func (action);
|
||
}
|
||
|
||
static GList *
|
||
add_gvalue_to_list_as_struct (gpointer source, GList * list, const GValue * v)
|
||
{
|
||
if (G_VALUE_HOLDS_STRING (v)) {
|
||
GstStructure *structure =
|
||
gst_structure_new_from_string (g_value_get_string (v));
|
||
|
||
if (!structure)
|
||
gst_validate_error_structure (source, "Invalid structure: %s",
|
||
g_value_get_string (v));
|
||
|
||
return g_list_append (list, structure);
|
||
}
|
||
|
||
if (GST_VALUE_HOLDS_STRUCTURE (v))
|
||
return g_list_append (list,
|
||
gst_structure_copy (gst_value_get_structure (v)));
|
||
|
||
|
||
gst_validate_error_structure (source, "Expected a string or a structure,"
|
||
" got %s instead", gst_value_serialize (v));
|
||
return NULL;
|
||
}
|
||
|
||
static GList *
|
||
gst_validate_utils_get_structures (gpointer source,
|
||
GstStructure * str, const gchar * fieldname)
|
||
{
|
||
guint i, size;
|
||
GList *res = NULL;
|
||
const GValue *value = gst_structure_get_value (str, fieldname);
|
||
|
||
if (!value)
|
||
return NULL;
|
||
|
||
if (G_VALUE_HOLDS_STRING (value) || GST_VALUE_HOLDS_STRUCTURE (value))
|
||
return add_gvalue_to_list_as_struct (source, res, value);
|
||
|
||
if (!GST_VALUE_HOLDS_LIST (value) && !GST_VALUE_HOLDS_ARRAY (value)) {
|
||
g_error ("%s must have type list of structure/string (or a string), "
|
||
"e.g. %s={ [struct1, a=val1], [struct2, a=val2] }, got: \"%s\" in %s",
|
||
fieldname, fieldname, gst_value_serialize (value),
|
||
gst_structure_to_string (str));
|
||
return NULL;
|
||
}
|
||
|
||
size =
|
||
GST_VALUE_HOLDS_LIST (value) ? gst_value_list_get_size (value) :
|
||
gst_value_array_get_size (value);
|
||
for (i = 0; i < size; i++)
|
||
res =
|
||
add_gvalue_to_list_as_struct (source, res,
|
||
GST_VALUE_HOLDS_LIST (value) ?
|
||
gst_value_list_get_value (value, i) :
|
||
gst_value_array_get_value (value, i));
|
||
|
||
return res;
|
||
}
|
||
|
||
static GstValidateAction *
|
||
gst_validate_create_subaction (GstValidateScenario * scenario,
|
||
GstStructure * lvariables, GstValidateAction * action,
|
||
GstStructure * nstruct, gint it, gint max)
|
||
{
|
||
GstValidateAction *subaction;
|
||
GstValidateActionType *action_type =
|
||
_find_action_type (gst_structure_get_name (nstruct));
|
||
|
||
if (!action_type)
|
||
gst_validate_error_structure (action,
|
||
"Unknown action type: '%s'", gst_structure_get_name (nstruct));
|
||
subaction = gst_validate_action_new (scenario, action_type, nstruct, FALSE);
|
||
GST_VALIDATE_ACTION_RANGE_NAME (subaction) =
|
||
g_strdup (GST_VALIDATE_ACTION_RANGE_NAME (action));
|
||
GST_VALIDATE_ACTION_FILENAME (subaction) =
|
||
g_strdup (GST_VALIDATE_ACTION_FILENAME (action));
|
||
GST_VALIDATE_ACTION_DEBUG (subaction) =
|
||
g_strdup (GST_VALIDATE_ACTION_DEBUG (action));
|
||
GST_VALIDATE_ACTION_LINENO (subaction) = GST_VALIDATE_ACTION_LINENO (action);
|
||
subaction->repeat = it;
|
||
subaction->priv->subaction_level = action->priv->subaction_level + 1;
|
||
GST_VALIDATE_ACTION_N_REPEATS (subaction) = max;
|
||
gst_validate_structure_resolve_variables (subaction, subaction->structure,
|
||
lvariables,
|
||
GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_LOCAL_ONLY |
|
||
GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_NO_FAILURE |
|
||
GST_VALIDATE_STRUCTURE_RESOLVE_VARIABLES_NO_EXPRESSION);
|
||
gst_structure_free (nstruct);
|
||
|
||
return subaction;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
gst_validate_foreach_prepare (GstValidateAction * action)
|
||
{
|
||
gint it, i;
|
||
gint min = 0, max = 1, step = 1;
|
||
const GValue *it_array = NULL;
|
||
GstValidateScenario *scenario;
|
||
GList *actions, *tmp;
|
||
|
||
scenario = gst_validate_action_get_scenario (action);
|
||
g_assert (scenario);
|
||
_update_well_known_vars (scenario);
|
||
gst_validate_action_setup_repeat (scenario, action);
|
||
|
||
g_free (GST_VALIDATE_ACTION_RANGE_NAME (action));
|
||
GST_VALIDATE_ACTION_RANGE_NAME (action) = NULL;
|
||
gst_structure_foreach_id_str (action->structure,
|
||
(GstStructureForeachIdStrFunc) _foreach_find_iterator, action);
|
||
|
||
/* Allow using the repeat field here too */
|
||
if (!GST_VALIDATE_ACTION_RANGE_NAME (action)
|
||
&& !GST_VALIDATE_ACTION_N_REPEATS (action))
|
||
gst_validate_error_structure (action, "Missing range specifier field.");
|
||
|
||
if (GST_VALIDATE_ACTION_RANGE_NAME (action)) {
|
||
const GValue *it_value = gst_structure_get_value (action->structure,
|
||
GST_VALIDATE_ACTION_RANGE_NAME (action));
|
||
|
||
if (GST_VALUE_HOLDS_INT_RANGE (it_value)) {
|
||
min = gst_value_get_int_range_min (it_value);
|
||
max = gst_value_get_int_range_max (it_value);
|
||
step = gst_value_get_int_range_step (it_value);
|
||
|
||
if (min % step != 0)
|
||
gst_validate_error_structure (action,
|
||
"Range min[%d] must be a multiple of step[%d].", min, step);
|
||
|
||
if (max % step != 0)
|
||
gst_validate_error_structure (action,
|
||
"Range max[%d] must be a multiple of step[%d].", max, step);
|
||
} else {
|
||
it_array = it_value;
|
||
max = gst_value_array_get_size (it_array);
|
||
}
|
||
} else {
|
||
min = action->repeat;
|
||
max = action->repeat + 1;
|
||
}
|
||
|
||
actions = gst_validate_utils_get_structures (action, action->structure,
|
||
"actions");
|
||
i = g_list_index (scenario->priv->actions, action);
|
||
for (it = min; it < max; it = it + step) {
|
||
GstStructure *lvariables = gst_structure_new_empty ("vars");
|
||
const GValue *it_value = NULL;
|
||
|
||
if (it_array) {
|
||
it_value = gst_value_array_get_value (it_array, it);
|
||
|
||
gst_structure_set_value (lvariables,
|
||
GST_VALIDATE_ACTION_RANGE_NAME (action), it_value);
|
||
|
||
}
|
||
|
||
for (tmp = actions; tmp; tmp = tmp->next) {
|
||
GstValidateAction *subact =
|
||
gst_validate_create_subaction (scenario, lvariables, action,
|
||
gst_structure_copy (tmp->data), it, max);
|
||
scenario->priv->actions =
|
||
g_list_insert (scenario->priv->actions, subact, i++);
|
||
if (it_value) {
|
||
g_value_init (&subact->priv->it_value, G_VALUE_TYPE (it_value));
|
||
g_value_copy (it_value, &subact->priv->it_value);
|
||
}
|
||
}
|
||
|
||
gst_structure_free (lvariables);
|
||
}
|
||
g_list_free_full (actions, (GDestroyNotify) gst_structure_free);
|
||
|
||
scenario->priv->actions = g_list_remove (scenario->priv->actions, action);
|
||
gst_structure_remove_field (action->structure, "actions");
|
||
|
||
gst_object_unref (scenario);
|
||
return GST_VALIDATE_EXECUTE_ACTION_DONE;
|
||
}
|
||
|
||
static gboolean
|
||
_check_structure_has_expected_value (const GstIdStr * fieldname,
|
||
const GValue * value, GstStructure * message_struct)
|
||
{
|
||
const GValue *v = gst_structure_id_str_get_value (message_struct, fieldname);
|
||
|
||
if (!v) {
|
||
gst_structure_set (message_struct, "__validate_has_expected_values",
|
||
G_TYPE_BOOLEAN, FALSE, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
if (gst_value_compare (value, v) != GST_VALUE_EQUAL) {
|
||
gst_structure_set (message_struct, "__validate_has_expected_values",
|
||
G_TYPE_BOOLEAN, FALSE, NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
gst_structure_set (message_struct, "__validate_has_expected_values",
|
||
G_TYPE_BOOLEAN, TRUE, NULL);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
_check_waiting_for_message (GstValidateScenario * scenario,
|
||
GstMessage * message)
|
||
{
|
||
GstStructure *expected_values = NULL;
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
const gchar *message_type;
|
||
|
||
if (!priv->wait_message_action) {
|
||
GST_LOG_OBJECT (scenario, "Not waiting for message");
|
||
return;
|
||
}
|
||
|
||
message_type = gst_structure_get_string (priv->wait_message_action->structure,
|
||
"message-type");
|
||
|
||
if (g_strcmp0 (message_type, GST_MESSAGE_TYPE_NAME (message)))
|
||
return;
|
||
|
||
GST_LOG_OBJECT (scenario, " Waiting for %s and got %s", message_type,
|
||
GST_MESSAGE_TYPE_NAME (message));
|
||
|
||
gst_structure_get (priv->wait_message_action->structure, "expected-values",
|
||
GST_TYPE_STRUCTURE, &expected_values, NULL);
|
||
if (expected_values) {
|
||
gboolean res = FALSE;
|
||
GstStructure *message_struct =
|
||
(GstStructure *) gst_message_get_structure (message);
|
||
|
||
message_struct =
|
||
message_struct ? gst_structure_copy (message_struct) : NULL;
|
||
if (!message_struct) {
|
||
GST_DEBUG_OBJECT (scenario,
|
||
"Waiting for %" GST_PTR_FORMAT " but message has no structure.",
|
||
priv->wait_message_action->structure);
|
||
return;
|
||
}
|
||
|
||
gst_structure_set (message_struct, "__validate_has_expected_values",
|
||
G_TYPE_BOOLEAN, FALSE, NULL);
|
||
gst_structure_foreach_id_str (expected_values,
|
||
(GstStructureForeachIdStrFunc) _check_structure_has_expected_value,
|
||
message_struct);
|
||
|
||
if (!gst_structure_get_boolean (message_struct,
|
||
"__validate_has_expected_values", &res) || !res) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
gst_validate_action_set_done (priv->wait_message_action);
|
||
_add_execute_actions_gsource (scenario);
|
||
}
|
||
|
||
static gboolean
|
||
streams_list_contain (GList * streams, const gchar * stream_id)
|
||
{
|
||
GList *l;
|
||
|
||
for (l = streams; l; l = g_list_next (l)) {
|
||
GstStream *s = l->data;
|
||
|
||
if (!g_strcmp0 (s->stream_id, stream_id))
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_check_latency (GstValidateScenario * scenario,
|
||
GstElement * pipeline)
|
||
{
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
GstQuery *query;
|
||
GstClockTime min_latency;
|
||
|
||
query = gst_query_new_latency ();
|
||
if (!gst_element_query (GST_ELEMENT_CAST (pipeline), query)) {
|
||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Failed to perform LATENCY query");
|
||
gst_query_unref (query);
|
||
return;
|
||
}
|
||
|
||
gst_query_parse_latency (query, NULL, &min_latency, NULL);
|
||
gst_query_unref (query);
|
||
GST_DEBUG_OBJECT (scenario, "Pipeline latency: %" GST_TIME_FORMAT
|
||
" max allowed: %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (min_latency), GST_TIME_ARGS (priv->max_latency));
|
||
|
||
if (priv->max_latency != GST_CLOCK_TIME_NONE &&
|
||
min_latency > priv->max_latency) {
|
||
GST_VALIDATE_REPORT (scenario, CONFIG_LATENCY_TOO_HIGH,
|
||
"Pipeline latency is too high: %" GST_TIME_FORMAT " (max allowed %"
|
||
GST_TIME_FORMAT ")", GST_TIME_ARGS (min_latency),
|
||
GST_TIME_ARGS (priv->max_latency));
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
gst_validate_scenario_is_flush_seeking (GstValidateScenario * scenario)
|
||
{
|
||
GstValidateSeekInformation *seekinfo = scenario->priv->current_seek;
|
||
|
||
if (!seekinfo)
|
||
return FALSE;
|
||
|
||
if (!(seekinfo->flags & GST_SEEK_FLAG_FLUSH))
|
||
return FALSE;
|
||
|
||
return seekinfo->action->priv->state == GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_reset (GstValidateScenario * scenario)
|
||
{
|
||
/* Reset sink information */
|
||
SCENARIO_LOCK (scenario);
|
||
g_list_foreach (scenario->priv->sinks, (GFunc) _reset_sink_information, NULL);
|
||
/* Reset current seek */
|
||
scenario->priv->current_seek = NULL;
|
||
scenario->priv->current_seqnum = GST_SEQNUM_INVALID;
|
||
SCENARIO_UNLOCK (scenario);
|
||
}
|
||
|
||
typedef struct
|
||
{
|
||
GstValidateScenario *scenario;
|
||
GstMessage *message;
|
||
} MessageData;
|
||
|
||
static void
|
||
message_data_free (MessageData * d)
|
||
{
|
||
gst_message_unref (d->message);
|
||
gst_object_unref (d->scenario);
|
||
|
||
g_free (d);
|
||
}
|
||
|
||
static gboolean
|
||
handle_bus_message (MessageData * d)
|
||
{
|
||
gboolean is_error = FALSE;
|
||
GstMessage *message = d->message;
|
||
GstValidateScenario *scenario = d->scenario;
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
|
||
if (!pipeline) {
|
||
GST_ERROR_OBJECT (scenario, "No pipeline set anymore!");
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
GST_DEBUG_OBJECT (scenario, "message %" GST_PTR_FORMAT, message);
|
||
|
||
switch (GST_MESSAGE_TYPE (message)) {
|
||
case GST_MESSAGE_ASYNC_DONE:
|
||
if (!gst_validate_scenario_is_flush_seeking (scenario)
|
||
&& priv->needs_async_done) {
|
||
priv->needs_async_done = FALSE;
|
||
if (priv->actions && _action_sets_state (priv->actions->data)
|
||
&& !priv->changing_state)
|
||
gst_validate_action_set_done (priv->actions->data);
|
||
}
|
||
|
||
if (priv->needs_playback_parsing) {
|
||
priv->needs_playback_parsing = FALSE;
|
||
if (!gst_validate_parse_next_action_playback_time (scenario))
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
_add_execute_actions_gsource (scenario);
|
||
break;
|
||
case GST_MESSAGE_STATE_CHANGED:
|
||
{
|
||
GstState old_state, state, pending_state;
|
||
gboolean reached_state;
|
||
|
||
if (!pipeline || GST_MESSAGE_SRC (message) != GST_OBJECT (pipeline))
|
||
break;
|
||
|
||
gst_message_parse_state_changed (message, &old_state, &state,
|
||
&pending_state);
|
||
|
||
reached_state = pending_state == GST_STATE_VOID_PENDING;
|
||
|
||
if (old_state == GST_STATE_PAUSED && state == GST_STATE_READY)
|
||
gst_validate_scenario_reset (scenario);
|
||
|
||
if (reached_state && gst_validate_scenario_is_flush_seeking (scenario))
|
||
gst_validate_action_set_done (priv->current_seek->action);
|
||
|
||
if (priv->changing_state && priv->target_state == state) {
|
||
priv->changing_state = FALSE;
|
||
if (priv->actions && _action_sets_state (priv->actions->data)
|
||
&& reached_state)
|
||
gst_validate_action_set_done (priv->actions->data);
|
||
}
|
||
|
||
if (old_state == scenario->priv->target_state - 1
|
||
&& state == scenario->priv->target_state)
|
||
_add_execute_actions_gsource (scenario);
|
||
|
||
/* GstBin only send a new latency message when reaching PLAYING if
|
||
* async-handling=true so check the latency manually. */
|
||
if (state == GST_STATE_PLAYING)
|
||
gst_validate_scenario_check_latency (scenario, pipeline);
|
||
break;
|
||
}
|
||
case GST_MESSAGE_ERROR:
|
||
is_error = TRUE;
|
||
|
||
/* FALLTHROUGH */
|
||
case GST_MESSAGE_EOS:
|
||
{
|
||
GstValidateAction *stop_action;
|
||
GstValidateActionType *stop_action_type;
|
||
GstStructure *s;
|
||
|
||
if (is_error && priv->allow_errors) {
|
||
#ifndef GST_DISABLE_GST_DEBUG
|
||
GError *err = NULL;
|
||
gchar *dbg_info = NULL;
|
||
gst_message_parse_error (message, &err, &dbg_info);
|
||
|
||
GST_INFO_OBJECT (scenario, "Got expected error %" GST_PTR_FORMAT,
|
||
message);
|
||
g_clear_error (&err);
|
||
g_free (dbg_info);
|
||
#endif
|
||
|
||
if (scenario->priv->needs_async_done || scenario->priv->changing_state) {
|
||
if (scenario->priv->actions) {
|
||
GstValidateAction *act =
|
||
gst_validate_action_ref (scenario->priv->actions->data);
|
||
|
||
GST_VALIDATE_REPORT_ACTION (scenario, act,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Error message happened while executing action");
|
||
if (scenario->priv->changing_state) {
|
||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||
}
|
||
act->priv->state = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
gst_validate_action_set_done (act);
|
||
gst_bus_set_flushing (scenario->priv->bus, TRUE);
|
||
gst_bus_set_flushing (scenario->priv->bus, FALSE);
|
||
|
||
gst_validate_action_unref (act);
|
||
}
|
||
|
||
scenario->priv->needs_async_done = scenario->priv->changing_state =
|
||
FALSE;
|
||
}
|
||
goto done;
|
||
}
|
||
|
||
GST_VALIDATE_SCENARIO_EOS_HANDLING_LOCK (scenario);
|
||
{
|
||
/* gst_validate_action_set_done() does not finish the action
|
||
* immediately. Instead, it posts a task to the main thread to do most
|
||
* of the work in _action_set_done().
|
||
*
|
||
* While the EOS handling lock guarantees that if an action had to call
|
||
* gst_validate_action_set_done() it has done so, it does not guarantee
|
||
* that _action_set_done() has been called.
|
||
*
|
||
* Is it possible that this handler is run before _action_set_done(), so
|
||
* we check at this point for actions that have a pending_set_done and
|
||
* call it before continuing. */
|
||
GList *actions = g_list_copy_deep (priv->actions,
|
||
(GCopyFunc) (gst_validate_action_ref), NULL);
|
||
GList *i;
|
||
for (i = actions; i; i = i->next) {
|
||
GstValidateAction *action = (GstValidateAction *) i->data;
|
||
if (action->priv->pending_set_done)
|
||
_action_set_done (action);
|
||
}
|
||
g_list_free_full (actions, (GDestroyNotify) gst_validate_action_unref);
|
||
}
|
||
|
||
if (!is_error) {
|
||
if (priv->ignore_eos) {
|
||
GST_INFO_OBJECT (scenario,
|
||
"Got EOS but ignoring it, executing next action?");
|
||
GST_VALIDATE_SCENARIO_EOS_HANDLING_UNLOCK (scenario);
|
||
goto done;
|
||
}
|
||
|
||
priv->got_eos = TRUE;
|
||
if (priv->wait_message_action) {
|
||
|
||
if (priv->actions && priv->actions->next) {
|
||
GST_DEBUG_OBJECT (scenario,
|
||
"Waiting for a message and got a next action"
|
||
" to execute, letting it a chance!");
|
||
GST_VALIDATE_SCENARIO_EOS_HANDLING_UNLOCK (scenario);
|
||
goto done;
|
||
} else {
|
||
/* Clear current message wait if waiting for EOS */
|
||
_check_waiting_for_message (scenario, message);
|
||
}
|
||
}
|
||
}
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
/* Make sure that if there is an ASYNC_DONE in the message queue, we do not
|
||
take it into account */
|
||
g_list_free_full (priv->seeks,
|
||
(GDestroyNotify) gst_validate_seek_information_free);
|
||
priv->seeks = NULL;
|
||
priv->current_seek = NULL;
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
GST_DEBUG_OBJECT (scenario, "Got EOS; generate 'stop' action");
|
||
|
||
stop_action_type = _find_action_type ("stop");
|
||
s = gst_structure_new ("stop", "generated-after-eos", G_TYPE_BOOLEAN,
|
||
!is_error, "generated-after-error", G_TYPE_BOOLEAN, is_error, NULL);
|
||
stop_action = gst_validate_action_new (scenario, stop_action_type,
|
||
s, FALSE);
|
||
gst_structure_free (s);
|
||
gst_validate_execute_action (stop_action_type, stop_action);
|
||
gst_mini_object_unref (GST_MINI_OBJECT (stop_action));
|
||
|
||
GST_VALIDATE_SCENARIO_EOS_HANDLING_UNLOCK (scenario);
|
||
break;
|
||
}
|
||
case GST_MESSAGE_BUFFERING:
|
||
{
|
||
gint percent;
|
||
|
||
gst_message_parse_buffering (message, &percent);
|
||
|
||
if (percent == 100)
|
||
priv->buffering = FALSE;
|
||
else
|
||
priv->buffering = TRUE;
|
||
break;
|
||
}
|
||
case GST_MESSAGE_STREAMS_SELECTED:
|
||
{
|
||
guint i;
|
||
GList *streams_selected = NULL;
|
||
|
||
for (i = 0; i < gst_message_streams_selected_get_size (message); i++) {
|
||
GstStream *stream =
|
||
gst_message_streams_selected_get_stream (message, i);
|
||
|
||
streams_selected = g_list_append (streams_selected, stream);
|
||
}
|
||
|
||
/* Is there a pending switch-track action waiting for the new streams to
|
||
* be selected? */
|
||
if (priv->pending_switch_track) {
|
||
GList *expected, *l;
|
||
GstValidateScenario *scenario =
|
||
gst_validate_action_get_scenario (priv->pending_switch_track);
|
||
|
||
expected =
|
||
gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST
|
||
(priv->pending_switch_track), ACTION_EXPECTED_STREAM_QUARK);
|
||
|
||
if (g_list_length (expected) != g_list_length (streams_selected)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, priv->pending_switch_track,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Was expecting %d selected streams but got %d",
|
||
g_list_length (expected), g_list_length (streams_selected));
|
||
goto action_done;
|
||
}
|
||
|
||
for (l = expected; l; l = g_list_next (l)) {
|
||
const gchar *stream_id = l->data;
|
||
|
||
if (!streams_list_contain (streams_selected, stream_id)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, priv->pending_switch_track,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Stream %s has not be activated", stream_id);
|
||
goto action_done;
|
||
}
|
||
}
|
||
|
||
action_done:
|
||
gst_object_unref (scenario);
|
||
gst_validate_action_set_done (priv->pending_switch_track);
|
||
priv->pending_switch_track = NULL;
|
||
}
|
||
|
||
g_list_free_full (streams_selected, gst_object_unref);
|
||
break;
|
||
}
|
||
case GST_MESSAGE_LATENCY:
|
||
gst_validate_scenario_check_latency (scenario, pipeline);
|
||
break;
|
||
|
||
case GST_MESSAGE_QOS:
|
||
{
|
||
guint64 dropped;
|
||
|
||
/* Check the maximum allowed when scenario is terminating so the report
|
||
* will include the actual number of dropped buffers. */
|
||
gst_message_parse_qos_stats (message, NULL, NULL, &dropped);
|
||
if (dropped != -1)
|
||
priv->dropped = dropped;
|
||
break;
|
||
}
|
||
case GST_MESSAGE_APPLICATION:
|
||
{
|
||
const GstStructure *s;
|
||
s = gst_message_get_structure (message);
|
||
if (gst_structure_has_name (s, "validate-segment")) {
|
||
GstValidateSinkInformation *sink_info;
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
sink_info =
|
||
_find_sink_information (scenario,
|
||
(GstElement *) GST_MESSAGE_SRC (message));
|
||
|
||
if (sink_info) {
|
||
const GValue *segment_value;
|
||
const GstSegment *segment;
|
||
|
||
GST_DEBUG_OBJECT (scenario, "Got segment update for %s",
|
||
GST_ELEMENT_NAME (sink_info->sink));
|
||
sink_info->segment_seqnum = GST_MESSAGE_SEQNUM (message);
|
||
segment_value = gst_structure_get_value (s, "segment");
|
||
g_assert (segment_value != NULL);
|
||
segment = (const GstSegment *) g_value_get_boxed (segment_value);
|
||
gst_segment_copy_into (segment, &sink_info->segment);
|
||
_validate_sink_information (scenario);
|
||
}
|
||
SCENARIO_UNLOCK (scenario);
|
||
}
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
|
||
done:
|
||
gst_object_unref (pipeline);
|
||
/* Check if we got the message expected by a wait action */
|
||
_check_waiting_for_message (scenario, message);
|
||
|
||
execute_next_action_full (scenario, message);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static void
|
||
message_cb (GstBus * bus, GstMessage * message, GstValidateScenario * scenario)
|
||
{
|
||
MessageData *d = g_new0 (MessageData, 1);
|
||
|
||
d->message = gst_message_ref (message);
|
||
d->scenario = gst_object_ref (scenario);
|
||
|
||
g_main_context_invoke_full (scenario->priv->context,
|
||
G_PRIORITY_DEFAULT_IDLE,
|
||
(GSourceFunc) handle_bus_message, d, (GDestroyNotify) message_data_free);
|
||
}
|
||
|
||
static void
|
||
runner_stopping (GstValidateRunner * runner, GstValidateScenario * scenario)
|
||
{
|
||
GstElement *pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
|
||
if (pipeline) {
|
||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||
gst_object_unref (pipeline);
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
_action_type_has_parameter (GstValidateActionType * atype,
|
||
const gchar * paramname)
|
||
{
|
||
gint i;
|
||
|
||
if (!atype->parameters)
|
||
return FALSE;
|
||
|
||
for (i = 0; atype->parameters[i].name; i++)
|
||
if (g_strcmp0 (atype->parameters[i].name, paramname) == 0)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
gst_validate_scenario_load_structures (GstValidateScenario * scenario,
|
||
GList * structures, gboolean * is_config, const gchar * origin_file)
|
||
{
|
||
gboolean ret = TRUE;
|
||
GList *tmp;
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
GList *config;
|
||
|
||
*is_config = FALSE;
|
||
|
||
if (!structures) {
|
||
GST_INFO_OBJECT (scenario, "No structures provided");
|
||
return FALSE;
|
||
}
|
||
|
||
for (tmp = structures; tmp; tmp = tmp->next) {
|
||
GstValidateAction *action;
|
||
GstValidateActionType *action_type;
|
||
const gchar *type;
|
||
gboolean on_clock = FALSE;
|
||
GstStructure *structure = (GstStructure *) tmp->data;
|
||
|
||
type = gst_structure_get_name (structure);
|
||
if (!g_strcmp0 (type, "description") || !g_strcmp0 (type, "meta")) {
|
||
const gchar *pipeline_name;
|
||
|
||
gst_structure_get_boolean (structure, "is-config", is_config);
|
||
gst_structure_get_boolean (structure, "handles-states",
|
||
&priv->handles_state);
|
||
if (!gst_structure_get_enum (structure, "target-state", GST_TYPE_STATE,
|
||
(gint *) & priv->target_state) && !priv->handles_state) {
|
||
priv->target_state = GST_STATE_PLAYING;
|
||
}
|
||
|
||
gst_structure_get_boolean (structure, "ignore-eos", &priv->ignore_eos);
|
||
gst_structure_get_boolean (structure, "ignore-invalid-positions",
|
||
&priv->ignore_invalid_positions);
|
||
gst_structure_get_boolean (structure, "allow-errors",
|
||
&priv->allow_errors);
|
||
gst_structure_get_boolean (structure, "actions-on-idle",
|
||
&priv->execute_on_idle);
|
||
|
||
|
||
pipeline_name = gst_structure_get_string (structure, "pipeline-name");
|
||
if (pipeline_name) {
|
||
g_free (priv->pipeline_name);
|
||
priv->pipeline_name = g_strdup (pipeline_name);
|
||
}
|
||
|
||
gst_validate_utils_get_clocktime (structure, "max-latency",
|
||
&priv->max_latency);
|
||
|
||
gst_structure_get_int (structure, "max-dropped", &priv->max_dropped);
|
||
gboolean monitor_all_pipelines = FALSE;
|
||
if (gst_structure_get_boolean (structure, "monitor-all-pipelines",
|
||
&monitor_all_pipelines) && monitor_all_pipelines) {
|
||
GstValidateRunner *runner =
|
||
gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (scenario));
|
||
|
||
g_assert (runner);
|
||
gst_validate_runner_set_monitor_all_pipelines (runner, TRUE);
|
||
gst_object_unref (runner);
|
||
|
||
}
|
||
|
||
scenario->description = gst_structure_copy (structure);
|
||
|
||
continue;
|
||
} else if (!(action_type = _find_action_type (type))) {
|
||
if (gst_structure_has_field (structure, "optional-action-type")) {
|
||
GST_INFO_OBJECT (scenario,
|
||
"Action type not found %s but marked as not mandatory", type);
|
||
continue;
|
||
}
|
||
|
||
gst_validate_error_structure (structure,
|
||
"Unknown action type: '%s'", type);
|
||
goto failed;
|
||
}
|
||
|
||
gst_structure_get_boolean (structure, "on-clock", &on_clock);
|
||
if ((!g_strcmp0 (type, "crank-clock") || on_clock) && !priv->clock)
|
||
priv->clock = GST_TEST_CLOCK (gst_test_clock_new ());
|
||
|
||
if (action_type->parameters) {
|
||
guint i;
|
||
|
||
for (i = 0; action_type->parameters[i].name; i++) {
|
||
if (action_type->parameters[i].mandatory &&
|
||
gst_structure_has_field (structure,
|
||
action_type->parameters[i].name) == FALSE) {
|
||
gst_validate_error_structure (structure,
|
||
"Mandatory field '%s' not present in structure: %" GST_PTR_FORMAT,
|
||
action_type->parameters[i].name, structure);
|
||
goto failed;
|
||
}
|
||
}
|
||
}
|
||
|
||
action = gst_validate_action_new (scenario, action_type, structure, TRUE);
|
||
if (action->priv->state == GST_VALIDATE_EXECUTE_ACTION_ERROR) {
|
||
GST_ERROR_OBJECT (scenario, "Newly created action: %" GST_PTR_FORMAT
|
||
" was in error state", structure);
|
||
|
||
goto failed;
|
||
}
|
||
|
||
action->action_number = priv->num_actions++;
|
||
|
||
if (action->priv->state == GST_VALIDATE_EXECUTE_ACTION_OK) {
|
||
GST_DEBUG_OBJECT (scenario,
|
||
"Unrefing action that has already been executed");
|
||
gst_validate_action_unref (action);
|
||
action = NULL;
|
||
}
|
||
}
|
||
|
||
/* max-latency and max-dropped can be overridden using config */
|
||
for (config = gst_validate_plugin_get_config (NULL); config;
|
||
config = g_list_next (config)) {
|
||
GstClockTime max_latency;
|
||
|
||
gst_validate_utils_get_clocktime (config->data, "max-latency",
|
||
&max_latency);
|
||
if (GST_CLOCK_TIME_IS_VALID (max_latency))
|
||
priv->max_latency = max_latency;
|
||
|
||
gst_structure_get_int (config->data, "max-dropped", &priv->max_dropped);
|
||
}
|
||
|
||
done:
|
||
g_list_free_full (structures, (GDestroyNotify) gst_structure_free);
|
||
|
||
return ret;
|
||
|
||
failed:
|
||
ret = FALSE;
|
||
|
||
goto done;
|
||
}
|
||
|
||
gchar **
|
||
gst_validate_scenario_get_include_paths (const gchar * relative_scenario)
|
||
{
|
||
gint n;
|
||
gchar **env_scenariodir;
|
||
gchar *scenarios_path = g_strdup (g_getenv ("GST_VALIDATE_SCENARIOS_PATH"));
|
||
|
||
if (relative_scenario) {
|
||
gchar *relative_dir = g_path_get_dirname (relative_scenario);
|
||
gchar *tmp_scenarios_path =
|
||
g_strdup_printf ("%s%c%s", scenarios_path, G_SEARCHPATH_SEPARATOR,
|
||
relative_dir);
|
||
g_free (relative_dir);
|
||
|
||
g_free (scenarios_path);
|
||
scenarios_path = tmp_scenarios_path;
|
||
}
|
||
|
||
env_scenariodir =
|
||
scenarios_path ? g_strsplit (scenarios_path, G_SEARCHPATH_SEPARATOR_S,
|
||
0) : NULL;
|
||
g_free (scenarios_path);
|
||
|
||
n = env_scenariodir ? g_strv_length (env_scenariodir) : 0;
|
||
env_scenariodir = g_realloc_n (env_scenariodir, n + 3, sizeof (gchar *));
|
||
env_scenariodir[n] = g_build_filename (g_get_user_data_dir (),
|
||
"gstreamer-" GST_API_VERSION, "validate",
|
||
GST_VALIDATE_SCENARIO_DIRECTORY, NULL);
|
||
env_scenariodir[n + 1] =
|
||
g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION, "validate",
|
||
GST_VALIDATE_SCENARIO_DIRECTORY, NULL);
|
||
env_scenariodir[n + 2] = NULL;
|
||
|
||
return env_scenariodir;
|
||
}
|
||
|
||
static gboolean
|
||
_load_scenario_file (GstValidateScenario * scenario,
|
||
gchar * scenario_file, gboolean * is_config)
|
||
{
|
||
return gst_validate_scenario_load_structures (scenario,
|
||
gst_validate_utils_structs_parse_from_filename (scenario_file,
|
||
(GstValidateGetIncludePathsFunc)
|
||
gst_validate_scenario_get_include_paths, NULL), is_config,
|
||
scenario_file);
|
||
}
|
||
|
||
static gboolean
|
||
gst_validate_scenario_load (GstValidateScenario * scenario,
|
||
const gchar * scenario_name)
|
||
{
|
||
gchar **scenarios = NULL;
|
||
guint i;
|
||
gboolean found_actions = FALSE, is_config, ret = FALSE;
|
||
gchar **include_paths = gst_validate_scenario_get_include_paths (NULL);
|
||
|
||
if (!scenario_name)
|
||
goto invalid_name;
|
||
|
||
scenarios = g_strsplit (scenario_name, ":", -1);
|
||
|
||
for (i = 0; scenarios[i]; i++) {
|
||
guint include_i;
|
||
gchar *lfilename = NULL, *tldir = NULL, *scenario_file = NULL;
|
||
|
||
ret = FALSE;
|
||
|
||
/* First check if the scenario name is not a full path to the
|
||
* actual scenario */
|
||
if (g_file_test (scenarios[i], G_FILE_TEST_IS_REGULAR)) {
|
||
GST_DEBUG_OBJECT (scenario, "Scenario: %s is a full path to a scenario. "
|
||
"Trying to load it", scenarios[i]);
|
||
if ((ret = _load_scenario_file (scenario, scenarios[i], &is_config))) {
|
||
scenario_file = scenarios[i];
|
||
goto check_scenario;
|
||
}
|
||
}
|
||
|
||
if (g_str_has_suffix (scenarios[i], GST_VALIDATE_SCENARIO_SUFFIX))
|
||
lfilename = g_strdup (scenarios[i]);
|
||
else
|
||
lfilename =
|
||
g_strdup_printf ("%s" GST_VALIDATE_SCENARIO_SUFFIX, scenarios[i]);
|
||
|
||
for (include_i = 0; include_paths[include_i]; include_i++) {
|
||
tldir = g_build_filename (include_paths[include_i], lfilename, NULL);
|
||
if ((ret = _load_scenario_file (scenario, tldir, &is_config))) {
|
||
scenario_file = tldir;
|
||
break;
|
||
}
|
||
g_free (tldir);
|
||
}
|
||
|
||
if (!ret)
|
||
goto error;
|
||
|
||
/* else check scenario */
|
||
check_scenario:
|
||
if (!is_config) {
|
||
gchar *scenario_dir = g_path_get_dirname (scenario_file);
|
||
gchar *scenario_fname = g_path_get_basename (scenario_file);
|
||
gchar **scenario_name =
|
||
g_regex_split_simple ("\\.scenario", scenario_fname, 0, 0);
|
||
|
||
gst_structure_set (scenario->priv->vars,
|
||
"SCENARIO_DIR", G_TYPE_STRING, scenario_dir,
|
||
"SCENARIO_NAME", G_TYPE_STRING, scenario_name[0],
|
||
"SCENARIO_PATH", G_TYPE_STRING, scenario_file, NULL);
|
||
|
||
g_free (scenario_dir);
|
||
g_free (scenario_fname);
|
||
g_strfreev (scenario_name);
|
||
}
|
||
|
||
g_free (tldir);
|
||
g_free (lfilename);
|
||
|
||
if (!is_config) {
|
||
if (found_actions == TRUE)
|
||
goto one_actions_scenario_max;
|
||
else
|
||
found_actions = TRUE;
|
||
}
|
||
}
|
||
|
||
done:
|
||
|
||
if (include_paths)
|
||
g_strfreev (include_paths);
|
||
|
||
g_strfreev (scenarios);
|
||
|
||
if (ret == FALSE)
|
||
gst_validate_abort ("Could not set scenario %s => EXIT\n", scenario_name);
|
||
|
||
return ret;
|
||
|
||
invalid_name:
|
||
{
|
||
GST_ERROR ("Invalid name for scenario '%s'", GST_STR_NULL (scenario_name));
|
||
error:
|
||
ret = FALSE;
|
||
goto done;
|
||
}
|
||
one_actions_scenario_max:
|
||
{
|
||
GST_ERROR ("You can set at most only one action scenario. "
|
||
"You can have several config scenarios though (a config scenario's "
|
||
"file must have is-config=true, and all its actions must be executable "
|
||
"at parsing time).");
|
||
ret = FALSE;
|
||
goto done;
|
||
|
||
}
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_stop_http_servers (GstValidateScenario * scenario)
|
||
{
|
||
if (scenario->priv->http_servers) {
|
||
for (guint i = 0; i < scenario->priv->http_servers->len; i++) {
|
||
HTTPServer *server =
|
||
&g_array_index (scenario->priv->http_servers, HTTPServer, i);
|
||
if (server->subprocess) {
|
||
GError *error = NULL;
|
||
g_subprocess_force_exit (server->subprocess);
|
||
|
||
if (!g_subprocess_wait_check (server->subprocess, NULL, &error)) {
|
||
GST_WARNING_OBJECT (scenario,
|
||
"Error waiting for subprocess to exit: %s",
|
||
error ? error->message : "unknown error");
|
||
g_clear_error (&error);
|
||
}
|
||
|
||
g_clear_object (&server->subprocess);
|
||
}
|
||
}
|
||
g_array_free (scenario->priv->http_servers, TRUE);
|
||
scenario->priv->http_servers = NULL;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_set_property (GObject * object, guint prop_id,
|
||
const GValue * value, GParamSpec * pspec)
|
||
{
|
||
GstValidateScenario *self = GST_VALIDATE_SCENARIO (object);
|
||
|
||
switch (prop_id) {
|
||
case PROP_RUNNER:
|
||
{
|
||
GstValidateRunner *runner = g_value_get_object (value);
|
||
/* we assume the runner is valid as long as this scenario is,
|
||
* no ref taken */
|
||
gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (object), runner);
|
||
|
||
g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), self);
|
||
break;
|
||
}
|
||
case PROP_HANDLES_STATE:
|
||
g_assert_not_reached ();
|
||
break;
|
||
case PROP_EXECUTE_ON_IDLE:
|
||
self->priv->execute_on_idle = g_value_get_boolean (value);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_get_property (GObject * object, guint prop_id,
|
||
GValue * value, GParamSpec * pspec)
|
||
{
|
||
GstValidateScenario *self = GST_VALIDATE_SCENARIO (object);
|
||
|
||
switch (prop_id) {
|
||
case PROP_RUNNER:
|
||
/* we assume the runner is valid as long as this scenario is,
|
||
* no ref taken */
|
||
g_value_take_object (value,
|
||
gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object)));
|
||
break;
|
||
case PROP_HANDLES_STATE:
|
||
g_value_set_boolean (value, self->priv->handles_state);
|
||
break;
|
||
case PROP_EXECUTE_ON_IDLE:
|
||
g_value_set_boolean (value, self->priv->execute_on_idle);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_class_init (GstValidateScenarioClass * klass)
|
||
{
|
||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
||
object_class->dispose = gst_validate_scenario_dispose;
|
||
object_class->finalize = gst_validate_scenario_finalize;
|
||
|
||
object_class->get_property = gst_validate_scenario_get_property;
|
||
object_class->set_property = gst_validate_scenario_set_property;
|
||
|
||
g_object_class_install_property (object_class, PROP_RUNNER,
|
||
g_param_spec_object ("validate-runner", "VALIDATE Runner",
|
||
"The Validate runner to report errors to",
|
||
GST_TYPE_VALIDATE_RUNNER,
|
||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
|
||
|
||
g_object_class_install_property (object_class, PROP_HANDLES_STATE,
|
||
g_param_spec_boolean ("handles-states", "Handles state",
|
||
"True if the application should not handle the first state change. "
|
||
"False if it is application responsibility",
|
||
FALSE, G_PARAM_READABLE));
|
||
|
||
g_object_class_install_property (object_class,
|
||
PROP_EXECUTE_ON_IDLE,
|
||
g_param_spec_boolean ("execute-on-idle",
|
||
"Force waiting between actions",
|
||
"Always execute actions on idle and do not chain them to execute as"
|
||
" fast as possible. Setting this property is useful if action"
|
||
" execution can lead to the addition of new sources on the same main"
|
||
" loop as it provides these new GSource a chance to be dispatched"
|
||
" between actions", FALSE, G_PARAM_READWRITE));
|
||
|
||
/**
|
||
* GstValidateScenario::done:
|
||
* @scenario: The scenario running
|
||
*
|
||
* Emitted once all actions have been executed
|
||
*/
|
||
scenario_signals[DONE] =
|
||
g_signal_new ("done", G_TYPE_FROM_CLASS (klass),
|
||
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||
|
||
/**
|
||
* GstValidateScenario::action-done:
|
||
* @scenario: The scenario running
|
||
* @action: The #GstValidateAction that is done running
|
||
*
|
||
* Emitted when an action is done.
|
||
*
|
||
* Since: 1.20
|
||
*/
|
||
scenario_signals[ACTION_DONE] =
|
||
g_signal_new ("action-done", G_TYPE_FROM_CLASS (klass),
|
||
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1,
|
||
GST_TYPE_VALIDATE_ACTION);
|
||
|
||
/**
|
||
* GstValidateScenario::stopping:
|
||
* @scenario: The scenario that is being stopped
|
||
*
|
||
* Emitted when the 'stop' action is fired
|
||
*
|
||
* Since: 1.26
|
||
*/
|
||
scenario_signals[STOPPING] =
|
||
g_signal_new ("stopping", G_TYPE_FROM_CLASS (klass),
|
||
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_init (GstValidateScenario * scenario)
|
||
{
|
||
GstValidateScenarioPrivate *priv = scenario->priv =
|
||
gst_validate_scenario_get_instance_private (scenario);
|
||
|
||
priv->seek_pos_tol = DEFAULT_SEEK_TOLERANCE;
|
||
priv->segment_start = 0;
|
||
priv->segment_stop = GST_CLOCK_TIME_NONE;
|
||
priv->current_seek = NULL;
|
||
priv->current_seqnum = GST_SEQNUM_INVALID;
|
||
priv->action_execution_interval = 10;
|
||
priv->vars = gst_structure_new_empty ("vars");
|
||
priv->needs_playback_parsing = TRUE;
|
||
g_weak_ref_init (&scenario->priv->ref_pipeline, NULL);
|
||
priv->max_latency = GST_CLOCK_TIME_NONE;
|
||
priv->max_dropped = -1;
|
||
priv->clock = NULL;
|
||
|
||
g_mutex_init (&priv->lock);
|
||
|
||
scenario->priv->context = g_main_context_get_thread_default ();
|
||
if (!scenario->priv->context)
|
||
scenario->priv->context = g_main_context_default ();
|
||
g_main_context_ref (scenario->priv->context);
|
||
}
|
||
|
||
|
||
static void
|
||
gst_validate_scenario_dispose (GObject * object)
|
||
{
|
||
GstValidateScenarioPrivate *priv = GST_VALIDATE_SCENARIO (object)->priv;
|
||
GstValidateRunner *runner =
|
||
gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object));
|
||
|
||
g_signal_handlers_disconnect_by_func (runner, runner_stopping, object);
|
||
g_weak_ref_clear (&priv->ref_pipeline);
|
||
|
||
if (priv->bus) {
|
||
gst_bus_remove_signal_watch (priv->bus);
|
||
gst_object_unref (priv->bus);
|
||
priv->bus = NULL;
|
||
}
|
||
|
||
gst_object_replace ((GstObject **) & priv->clock, NULL);
|
||
gst_object_unref (runner);
|
||
|
||
G_OBJECT_CLASS (gst_validate_scenario_parent_class)->dispose (object);
|
||
}
|
||
|
||
static void
|
||
gst_validate_scenario_finalize (GObject * object)
|
||
{
|
||
GstValidateScenario *self = GST_VALIDATE_SCENARIO (object);
|
||
GstValidateScenarioPrivate *priv = self->priv;
|
||
|
||
/* Because g_object_add_weak_pointer() is used, this MUST be on the
|
||
* main thread. */
|
||
g_assert (g_main_context_acquire (priv->context));
|
||
g_main_context_release (priv->context);
|
||
|
||
g_main_context_unref (priv->context);
|
||
priv->context = NULL;
|
||
|
||
g_list_free_full (priv->seeks,
|
||
(GDestroyNotify) gst_validate_seek_information_free);
|
||
g_list_free_full (priv->sinks,
|
||
(GDestroyNotify) gst_validate_sink_information_free);
|
||
g_list_free_full (priv->actions, (GDestroyNotify) gst_validate_action_unref);
|
||
g_list_free_full (priv->non_blocking_running_actions,
|
||
(GDestroyNotify) gst_validate_action_unref);
|
||
g_list_free_full (priv->on_addition_actions,
|
||
(GDestroyNotify) gst_validate_action_unref);
|
||
g_free (priv->pipeline_name);
|
||
gst_structure_free (priv->vars);
|
||
if (self->description)
|
||
gst_structure_free (self->description);
|
||
g_mutex_clear (&priv->lock);
|
||
|
||
gst_validate_scenario_stop_http_servers (self);
|
||
|
||
G_OBJECT_CLASS (gst_validate_scenario_parent_class)->finalize (object);
|
||
}
|
||
|
||
static void _element_added_cb (GstBin * bin, GstElement * element,
|
||
GstValidateScenario * scenario);
|
||
static void _element_removed_cb (GstBin * bin, GstElement * element,
|
||
GstValidateScenario * scenario);
|
||
|
||
static void
|
||
iterate_children (GstValidateScenario * scenario, GstBin * bin)
|
||
{
|
||
GstIterator *it;
|
||
GValue v = G_VALUE_INIT;
|
||
gboolean done = FALSE;
|
||
GHashTable *called; /* set of GstElement on which we already called _element_added_cb() */
|
||
|
||
called = g_hash_table_new (NULL, NULL);
|
||
it = gst_bin_iterate_elements (bin);
|
||
|
||
while (!done) {
|
||
switch (gst_iterator_next (it, &v)) {
|
||
case GST_ITERATOR_OK:{
|
||
GstElement *child = g_value_get_object (&v);
|
||
|
||
if (g_hash_table_lookup (called, child) == NULL) {
|
||
_element_added_cb (bin, child, scenario);
|
||
g_hash_table_add (called, child);
|
||
}
|
||
g_value_reset (&v);
|
||
}
|
||
break;
|
||
case GST_ITERATOR_RESYNC:
|
||
gst_iterator_resync (it);
|
||
break;
|
||
case GST_ITERATOR_ERROR:
|
||
case GST_ITERATOR_DONE:
|
||
done = TRUE;
|
||
}
|
||
}
|
||
g_value_reset (&v);
|
||
gst_iterator_free (it);
|
||
g_hash_table_unref (called);
|
||
}
|
||
|
||
static gboolean
|
||
should_execute_action (GstElement * element, GstValidateAction * action)
|
||
{
|
||
return gst_validate_element_matches_target (element, action->structure);
|
||
}
|
||
|
||
/* Returns TRUE if:
|
||
* * The element has no parent (pipeline)
|
||
* * Or it's a sink*/
|
||
static gboolean
|
||
_all_parents_are_sink (GstElement * element)
|
||
{
|
||
if (GST_OBJECT_PARENT (element) == NULL)
|
||
return TRUE;
|
||
|
||
if (!GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SINK))
|
||
return FALSE;
|
||
|
||
return _all_parents_are_sink ((GstElement *) GST_OBJECT_PARENT (element));
|
||
}
|
||
|
||
static void
|
||
_element_removed_cb (GstBin * bin, GstElement * element,
|
||
GstValidateScenario * scenario)
|
||
{
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
|
||
if (GST_IS_BASE_SINK (element)) {
|
||
GstValidateSinkInformation *sink_info;
|
||
SCENARIO_LOCK (scenario);
|
||
sink_info = _find_sink_information (scenario, element);
|
||
if (sink_info) {
|
||
GST_DEBUG_OBJECT (scenario, "Removing sink information for %s",
|
||
GST_ELEMENT_NAME (element));
|
||
priv->sinks = g_list_remove (priv->sinks, sink_info);
|
||
gst_validate_sink_information_free (sink_info);
|
||
}
|
||
SCENARIO_UNLOCK (scenario);
|
||
}
|
||
}
|
||
|
||
static void
|
||
_element_added_cb (GstBin * bin, GstElement * element,
|
||
GstValidateScenario * scenario)
|
||
{
|
||
GList *tmp;
|
||
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
|
||
/* Check if it's an element we track for a set-property action */
|
||
SCENARIO_LOCK (scenario);
|
||
tmp = priv->on_addition_actions;
|
||
while (tmp) {
|
||
GstValidateAction *action = (GstValidateAction *) tmp->data;
|
||
|
||
if (action->playback_time != GST_CLOCK_TIME_NONE)
|
||
break;
|
||
if (g_strcmp0 (action->type, "set-property"))
|
||
break;
|
||
|
||
GST_DEBUG_OBJECT (bin, "Checking action #%d %p (%s)", action->action_number,
|
||
action, action->type);
|
||
if (should_execute_action (element, action)) {
|
||
GstValidateActionType *action_type;
|
||
action_type = _find_action_type (action->type);
|
||
GST_DEBUG_OBJECT (element, "Executing set-property action");
|
||
if (gst_validate_execute_action (action_type, action)) {
|
||
if (!gst_structure_has_field_typed (action->structure,
|
||
"on-all-instances", G_TYPE_BOOLEAN)) {
|
||
priv->on_addition_actions =
|
||
g_list_remove_link (priv->on_addition_actions, tmp);
|
||
gst_mini_object_unref (GST_MINI_OBJECT (action));
|
||
g_list_free (tmp);
|
||
tmp = priv->on_addition_actions;
|
||
} else {
|
||
tmp = tmp->next;
|
||
}
|
||
} else
|
||
tmp = tmp->next;
|
||
} else
|
||
tmp = tmp->next;
|
||
}
|
||
|
||
/* If it's a new GstBaseSink, add to list of sink information */
|
||
if (GST_IS_BASE_SINK (element) && _all_parents_are_sink (element)) {
|
||
GstValidateSinkInformation *sink_info =
|
||
g_new0 (GstValidateSinkInformation, 1);
|
||
GST_DEBUG_OBJECT (scenario, "Adding %s to list of tracked sinks",
|
||
GST_ELEMENT_NAME (element));
|
||
sink_info->sink = gst_object_ref (element);
|
||
priv->sinks = g_list_append (priv->sinks, sink_info);
|
||
}
|
||
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
/* If it's a bin, listen to the child */
|
||
if (GST_IS_BIN (element)) {
|
||
g_signal_connect (element, "element-added", (GCallback) _element_added_cb,
|
||
scenario);
|
||
g_signal_connect (element, "element-removed",
|
||
(GCallback) _element_removed_cb, scenario);
|
||
iterate_children (scenario, GST_BIN (element));
|
||
}
|
||
}
|
||
|
||
static GstValidateScenario *
|
||
gst_validate_scenario_new (GstValidateRunner *
|
||
runner, GstElement * pipeline, const gchar * scenario_name,
|
||
GList * structures)
|
||
{
|
||
GList *config;
|
||
gchar *name = g_strdup_printf ("%s-scenario", GST_OBJECT_NAME (pipeline));
|
||
GstValidateScenario *scenario =
|
||
g_object_new (GST_TYPE_VALIDATE_SCENARIO, "validate-runner",
|
||
runner, "name", name, NULL);
|
||
g_free (name);
|
||
|
||
g_object_ref_sink (scenario);
|
||
|
||
if (structures) {
|
||
gboolean is_config;
|
||
gst_validate_scenario_load_structures (scenario, structures, &is_config,
|
||
scenario_name);
|
||
} else {
|
||
|
||
GST_LOG ("Creating scenario %s", scenario_name);
|
||
if (!gst_validate_scenario_load (scenario, scenario_name)) {
|
||
g_object_unref (scenario);
|
||
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
if (scenario->priv->pipeline_name &&
|
||
!g_pattern_match_simple (scenario->priv->pipeline_name,
|
||
GST_OBJECT_NAME (pipeline))) {
|
||
GST_INFO ("Scenario %s only applies on pipeline %s not %s",
|
||
scenario_name, scenario->priv->pipeline_name,
|
||
GST_OBJECT_NAME (pipeline));
|
||
|
||
gst_object_unref (scenario);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
gst_validate_printf (NULL,
|
||
"**-> Running scenario %s on pipeline %s**\n", scenario_name,
|
||
GST_OBJECT_NAME (pipeline));
|
||
|
||
g_weak_ref_init (&scenario->priv->ref_pipeline, pipeline);
|
||
|
||
GstClockTime base_time, start_time;
|
||
gboolean use_system_clock = FALSE;
|
||
|
||
if (scenario->description) {
|
||
if (gst_validate_utils_get_clocktime (scenario->description, "base-time",
|
||
&base_time)) {
|
||
gst_validate_printf (NULL,
|
||
"**-> Setting %" GST_PTR_FORMAT " base time to %" GST_TIMEP_FORMAT
|
||
"**\n", pipeline, &base_time);
|
||
gst_element_set_base_time (GST_ELEMENT (pipeline), base_time);
|
||
}
|
||
|
||
if (gst_validate_utils_get_clocktime (scenario->description, "start-time",
|
||
&start_time)) {
|
||
gst_validate_printf (NULL,
|
||
"**-> Setting %" GST_PTR_FORMAT " start time to %" GST_TIMEP_FORMAT
|
||
"**\n", pipeline, &base_time);
|
||
gst_element_set_start_time (GST_ELEMENT (pipeline), start_time);
|
||
}
|
||
|
||
gst_structure_get_boolean (scenario->description, "use-system-clock",
|
||
&use_system_clock);
|
||
}
|
||
|
||
if (scenario->priv->clock) {
|
||
if (use_system_clock)
|
||
gst_validate_abort
|
||
("Requested to use system clock and test clock at the same time");
|
||
gst_element_set_clock (pipeline, GST_CLOCK_CAST (scenario->priv->clock));
|
||
gst_pipeline_use_clock (GST_PIPELINE (pipeline),
|
||
GST_CLOCK_CAST (scenario->priv->clock));
|
||
}
|
||
if (use_system_clock) {
|
||
GstClock *system_clock = gst_system_clock_obtain ();
|
||
gst_element_set_clock (pipeline, system_clock);
|
||
gst_pipeline_use_clock (GST_PIPELINE (pipeline), system_clock);
|
||
|
||
gst_validate_printf (NULL,
|
||
"**-> Using clock %" GST_PTR_FORMAT " on %" GST_PTR_FORMAT "**\n",
|
||
system_clock, pipeline);
|
||
}
|
||
|
||
gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (scenario),
|
||
g_filename_display_basename (scenario_name));
|
||
|
||
g_signal_connect (pipeline, "element-added", (GCallback) _element_added_cb,
|
||
scenario);
|
||
g_signal_connect (pipeline, "element-removed",
|
||
(GCallback) _element_removed_cb, scenario);
|
||
|
||
iterate_children (scenario, GST_BIN (pipeline));
|
||
|
||
scenario->priv->bus = gst_element_get_bus (pipeline);
|
||
gst_bus_add_signal_watch (scenario->priv->bus);
|
||
g_signal_connect (scenario->priv->bus, "message", (GCallback) message_cb,
|
||
scenario);
|
||
|
||
for (config = gst_validate_plugin_get_config (NULL); config;
|
||
config = config->next) {
|
||
gint interval;
|
||
|
||
if (gst_structure_get_uint (config->data,
|
||
"scenario-action-execution-interval",
|
||
&scenario->priv->action_execution_interval)) {
|
||
GST_DEBUG_OBJECT (scenario, "Setting action execution interval to %d",
|
||
scenario->priv->action_execution_interval);
|
||
if (scenario->priv->action_execution_interval > 0)
|
||
scenario->priv->execute_on_idle = TRUE;
|
||
break;
|
||
} else if (gst_structure_get_int (config->data,
|
||
"scenario-action-execution-interval", &interval)) {
|
||
if (interval > 0) {
|
||
scenario->priv->action_execution_interval = (guint) interval;
|
||
scenario->priv->execute_on_idle = TRUE;
|
||
GST_DEBUG_OBJECT (scenario, "Setting action execution interval to %d",
|
||
scenario->priv->action_execution_interval);
|
||
break;
|
||
} else {
|
||
GST_WARNING_OBJECT (scenario, "Interval is negative: %d", interval);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (scenario->priv->handles_state) {
|
||
GST_INFO_OBJECT (scenario, "Scenario handles state."
|
||
" Starting the get position source");
|
||
_add_execute_actions_gsource (scenario);
|
||
} else if (scenario->priv->target_state == GST_STATE_NULL) {
|
||
GST_INFO_OBJECT (scenario,
|
||
"Target state is NULL, starting action execution");
|
||
_add_execute_actions_gsource (scenario);
|
||
}
|
||
|
||
scenario->priv->overrides =
|
||
gst_validate_override_registry_get_override_for_names
|
||
(gst_validate_override_registry_get (), "scenarios", NULL);
|
||
|
||
return scenario;
|
||
}
|
||
|
||
GstValidateScenario *
|
||
gst_validate_scenario_from_structs (GstValidateRunner * runner,
|
||
GstElement * pipeline, GList * structures, const gchar * origin_file)
|
||
{
|
||
g_return_val_if_fail (structures, NULL);
|
||
|
||
return gst_validate_scenario_new (runner, pipeline, origin_file, structures);
|
||
}
|
||
|
||
/**
|
||
* gst_validate_scenario_factory_create:
|
||
* @runner: The #GstValidateRunner to use to report issues
|
||
* @pipeline: The pipeline to run the scenario on
|
||
* @scenario_name: The name (or path) of the scenario to run
|
||
*
|
||
* Returns: (transfer full) (nullable): A #GstValidateScenario or NULL
|
||
*/
|
||
GstValidateScenario *
|
||
gst_validate_scenario_factory_create (GstValidateRunner *
|
||
runner, GstElement * pipeline, const gchar * scenario_name)
|
||
{
|
||
return gst_validate_scenario_new (runner, pipeline, scenario_name, NULL);
|
||
}
|
||
|
||
static gboolean
|
||
_add_description (const GstIdStr * fieldname, const GValue * value,
|
||
KeyFileGroupName * kfg)
|
||
{
|
||
gchar *tmp = gst_value_serialize (value);
|
||
gchar *tmpcompress = g_strcompress (tmp);
|
||
|
||
g_key_file_set_string (kfg->kf, kfg->group_name,
|
||
gst_id_str_as_str (fieldname), tmpcompress);
|
||
|
||
g_free (tmpcompress);
|
||
g_free (tmp);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
gst_validate_scenario_check_and_set_needs_clock_sync (GList * structures,
|
||
GstStructure ** meta)
|
||
{
|
||
gboolean needs_clock_sync = FALSE;
|
||
GList *tmp;
|
||
|
||
for (tmp = structures; tmp; tmp = tmp->next) {
|
||
GstStructure *_struct = (GstStructure *) tmp->data;
|
||
gboolean is_meta = gst_structure_has_name (_struct, "description")
|
||
|| gst_structure_has_name (_struct, "meta");
|
||
|
||
if (!is_meta) {
|
||
GstValidateActionType *type =
|
||
_find_action_type (gst_structure_get_name (_struct));
|
||
|
||
if (type && type->flags & GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK)
|
||
needs_clock_sync = TRUE;
|
||
continue;
|
||
}
|
||
|
||
if (!*meta)
|
||
*meta = gst_structure_copy (_struct);
|
||
}
|
||
|
||
if (needs_clock_sync) {
|
||
if (*meta)
|
||
gst_structure_set (*meta, "need-clock-sync", G_TYPE_BOOLEAN, TRUE, NULL);
|
||
else
|
||
*meta = gst_structure_from_string ("description, need-clock-sync=true;",
|
||
NULL);
|
||
}
|
||
|
||
return needs_clock_sync;
|
||
}
|
||
|
||
static gboolean
|
||
_parse_scenario (GFile * f, GKeyFile * kf)
|
||
{
|
||
gboolean ret = FALSE;
|
||
gchar *path = g_file_get_path (f);
|
||
|
||
if (g_str_has_suffix (path, GST_VALIDATE_SCENARIO_SUFFIX)
|
||
|| g_str_has_suffix (path, GST_VALIDATE_VALIDATE_TEST_SUFFIX)) {
|
||
GstStructure *meta = NULL;
|
||
GList *tmp, *structures = gst_validate_structs_parse_from_gfile (f,
|
||
(GstValidateGetIncludePathsFunc)
|
||
gst_validate_scenario_get_include_paths);
|
||
|
||
gst_validate_scenario_check_and_set_needs_clock_sync (structures, &meta);
|
||
for (tmp = structures; tmp; tmp = tmp->next)
|
||
gst_structure_remove_fields (tmp->data, "__lineno__", "__filename__",
|
||
"__debug__", NULL);
|
||
|
||
if (meta) {
|
||
KeyFileGroupName kfg;
|
||
|
||
kfg.group_name = g_file_get_path (f);
|
||
kfg.kf = kf;
|
||
|
||
gst_structure_remove_fields (meta, "__lineno__", "__filename__",
|
||
"__debug__", NULL);
|
||
gst_structure_foreach_id_str (meta,
|
||
(GstStructureForeachIdStrFunc) _add_description, &kfg);
|
||
gst_structure_free (meta);
|
||
g_free (kfg.group_name);
|
||
} else {
|
||
g_key_file_set_string (kf, path, "noinfo", "nothing");
|
||
}
|
||
g_list_free_full (structures, (GDestroyNotify) gst_structure_free);
|
||
|
||
ret = TRUE;
|
||
}
|
||
|
||
g_free (path);
|
||
return ret;
|
||
}
|
||
|
||
static void
|
||
_list_scenarios_in_dir (GFile * dir, GKeyFile * kf)
|
||
{
|
||
GFileEnumerator *fenum;
|
||
GFileInfo *info;
|
||
|
||
fenum = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME,
|
||
G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
||
|
||
if (fenum == NULL)
|
||
return;
|
||
|
||
for (info = g_file_enumerator_next_file (fenum, NULL, NULL);
|
||
info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) {
|
||
GFile *f = g_file_enumerator_get_child (fenum, info);
|
||
|
||
_parse_scenario (f, kf);
|
||
gst_object_unref (f);
|
||
}
|
||
|
||
gst_object_unref (fenum);
|
||
}
|
||
|
||
gboolean
|
||
gst_validate_list_scenarios (gchar ** scenarios, gint num_scenarios,
|
||
gchar * output_file)
|
||
{
|
||
gchar *result;
|
||
gsize datalength;
|
||
|
||
GError *err = NULL;
|
||
GKeyFile *kf = NULL;
|
||
gint res = 0;
|
||
const gchar *envvar;
|
||
gchar **env_scenariodir = NULL;
|
||
gchar *tldir = g_build_filename (g_get_user_data_dir (),
|
||
"gstreamer-" GST_API_VERSION, "validate", GST_VALIDATE_SCENARIO_DIRECTORY,
|
||
NULL);
|
||
GFile *dir = g_file_new_for_path (tldir);
|
||
g_free (tldir);
|
||
|
||
kf = g_key_file_new ();
|
||
if (num_scenarios > 0) {
|
||
gint i;
|
||
GFile *file;
|
||
|
||
for (i = 0; i < num_scenarios; i++) {
|
||
file = g_file_new_for_path (scenarios[i]);
|
||
if (!_parse_scenario (file, kf)) {
|
||
GST_ERROR ("Could not parse scenario: %s", scenarios[i]);
|
||
|
||
res = 1;
|
||
}
|
||
g_clear_object (&file);
|
||
}
|
||
|
||
goto done;
|
||
}
|
||
|
||
envvar = g_getenv ("GST_VALIDATE_SCENARIOS_PATH");
|
||
if (envvar)
|
||
env_scenariodir = g_strsplit (envvar, ":", 0);
|
||
|
||
_list_scenarios_in_dir (dir, kf);
|
||
g_clear_object (&dir);
|
||
|
||
tldir = g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION,
|
||
"validate", GST_VALIDATE_SCENARIO_DIRECTORY, NULL);
|
||
dir = g_file_new_for_path (tldir);
|
||
_list_scenarios_in_dir (dir, kf);
|
||
g_clear_object (&dir);
|
||
g_free (tldir);
|
||
|
||
if (env_scenariodir) {
|
||
guint i;
|
||
GFile *subfile;
|
||
|
||
for (i = 0; env_scenariodir[i]; i++) {
|
||
subfile = g_file_new_for_path (env_scenariodir[i]);
|
||
_list_scenarios_in_dir (subfile, kf);
|
||
g_clear_object (&subfile);
|
||
}
|
||
}
|
||
|
||
/* Hack to make it work within the development environment */
|
||
dir = g_file_new_for_path ("data/scenarios");
|
||
_list_scenarios_in_dir (dir, kf);
|
||
g_clear_object (&dir);
|
||
|
||
done:
|
||
result = g_key_file_to_data (kf, &datalength, &err);
|
||
gst_validate_printf (NULL, "All scenarios available:\n%s", result);
|
||
|
||
if (output_file && !err) {
|
||
if (!g_file_set_contents (output_file, result, datalength, &err)) {
|
||
GST_WARNING ("Error writing to file '%s'", output_file);
|
||
}
|
||
}
|
||
|
||
g_free (result);
|
||
|
||
if (env_scenariodir)
|
||
g_strfreev (env_scenariodir);
|
||
|
||
if (err) {
|
||
GST_WARNING ("Got error '%s' listing scenarios", err->message);
|
||
g_clear_error (&err);
|
||
|
||
res = FALSE;
|
||
}
|
||
g_clear_object (&dir);
|
||
|
||
g_key_file_free (kf);
|
||
|
||
return res;
|
||
}
|
||
|
||
static GstValidateActionReturn
|
||
check_last_sample_internal (GstValidateScenario * scenario,
|
||
GstValidateAction * action, GstElement * sink)
|
||
{
|
||
GstSample *sample;
|
||
gchar *sum;
|
||
GstBuffer *buffer;
|
||
const gchar *target_sum;
|
||
guint64 frame_number;
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
GstVideoTimeCodeMeta *tc_meta;
|
||
|
||
g_object_get (sink, "last-sample", &sample, NULL);
|
||
if (sample == NULL) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not \"check-last-sample\" as %" GST_PTR_FORMAT
|
||
" 'last-sample' property is NULL"
|
||
". MAKE SURE THE 'enable-last-sample' PROPERTY IS SET TO 'TRUE'!",
|
||
sink);
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
buffer = gst_sample_get_buffer (sample);
|
||
target_sum = gst_structure_get_string (action->structure, "checksum");
|
||
if (target_sum) {
|
||
GstMapInfo map;
|
||
|
||
if (!gst_buffer_map (buffer, &map, GST_MAP_READ)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Last sample buffer could not be mapped, action can't run.");
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
sum = g_compute_checksum_for_data (G_CHECKSUM_SHA1, map.data, map.size);
|
||
gst_buffer_unmap (buffer, &map);
|
||
|
||
if (g_strcmp0 (sum, target_sum)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Last buffer checksum '%s' is different than the expected one: '%s'",
|
||
sum, target_sum);
|
||
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
g_free (sum);
|
||
|
||
goto done;
|
||
}
|
||
|
||
if (!gst_structure_get_uint64 (action->structure, "timecode-frame-number",
|
||
&frame_number)) {
|
||
gint iframe_number;
|
||
|
||
if (!gst_structure_get_int (action->structure, "timecode-frame-number",
|
||
&iframe_number)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"The 'checksum' or 'time-code-frame-number' parameters of the "
|
||
"`check-last-sample` action type needs to be specified, none found");
|
||
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
frame_number = (guint64) iframe_number;
|
||
}
|
||
|
||
tc_meta = gst_buffer_get_video_time_code_meta (buffer);
|
||
if (!tc_meta) {
|
||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not \"check-last-sample\" as the buffer doesn't contain a TimeCode"
|
||
" meta");
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
if (gst_video_time_code_frames_since_daily_jam (&tc_meta->tc) != frame_number) {
|
||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Last buffer frame number '%" G_GINT64_FORMAT
|
||
"' is different than the expected one: '%" G_GINT64_FORMAT "'",
|
||
gst_video_time_code_frames_since_daily_jam (&tc_meta->tc),
|
||
frame_number);
|
||
res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
done:
|
||
gst_sample_unref (sample);
|
||
return res;
|
||
}
|
||
|
||
static void
|
||
sink_last_sample_notify_cb (GstElement * sink, GParamSpec * arg G_GNUC_UNUSED,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
|
||
|
||
if (!scenario) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"No pipeline anymore, can't check last sample");
|
||
goto done;
|
||
}
|
||
|
||
check_last_sample_internal (scenario, action, sink);
|
||
gst_object_unref (scenario);
|
||
|
||
done:
|
||
g_signal_handlers_disconnect_by_func (sink, sink_last_sample_notify_cb,
|
||
action);
|
||
gst_validate_action_set_done (action);
|
||
gst_validate_action_unref (action);
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_check_last_sample_value (GstValidateScenario * scenario,
|
||
GstValidateAction * action, GstElement * sink)
|
||
{
|
||
GstSample *sample;
|
||
|
||
/* Connect before checking last sample to avoid a race where
|
||
* the sample is set between the time we connect and the time
|
||
* the time we get it */
|
||
g_signal_connect (sink, "notify::last-sample",
|
||
G_CALLBACK (sink_last_sample_notify_cb),
|
||
gst_validate_action_ref (action));
|
||
|
||
g_object_get (sink, "last-sample", &sample, NULL);
|
||
if (sample == NULL)
|
||
return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
gst_sample_unref (sample);
|
||
gst_validate_action_unref (action);
|
||
|
||
g_signal_handlers_disconnect_by_func (sink, sink_last_sample_notify_cb,
|
||
action);
|
||
|
||
return check_last_sample_internal (scenario, action, sink);
|
||
}
|
||
|
||
static gboolean
|
||
_sink_matches_last_sample_specs (GstElement * sink, const gchar * name,
|
||
const gchar * fname, GstCaps * sinkpad_caps)
|
||
{
|
||
GstCaps *tmpcaps;
|
||
GstPad *sinkpad;
|
||
GObjectClass *klass = G_OBJECT_GET_CLASS (sink);
|
||
GParamSpec *paramspec = g_object_class_find_property (klass, "last-sample");
|
||
|
||
if (!paramspec)
|
||
return FALSE;
|
||
|
||
if (paramspec->value_type != GST_TYPE_SAMPLE)
|
||
return FALSE;
|
||
|
||
if (!name && !fname && !sinkpad_caps)
|
||
return TRUE;
|
||
|
||
if (name && !g_strcmp0 (GST_OBJECT_NAME (sink), name))
|
||
return TRUE;
|
||
|
||
if (fname
|
||
&& !g_strcmp0 (GST_OBJECT_NAME (gst_element_get_factory (sink)), fname))
|
||
return TRUE;
|
||
|
||
if (!sinkpad_caps)
|
||
return FALSE;
|
||
|
||
sinkpad = gst_element_get_static_pad (sink, "sink");
|
||
if (!sinkpad)
|
||
return FALSE;
|
||
|
||
tmpcaps = gst_pad_get_current_caps (sinkpad);
|
||
if (tmpcaps) {
|
||
gboolean res = gst_caps_can_intersect (tmpcaps, sinkpad_caps);
|
||
|
||
GST_DEBUG_OBJECT (sink, "Matches caps: %" GST_PTR_FORMAT, tmpcaps);
|
||
gst_caps_unref (tmpcaps);
|
||
|
||
return res;
|
||
} else {
|
||
GST_INFO_OBJECT (sink, "No caps set yet, can't check it.");
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_check_last_sample (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstIterator *it;
|
||
GValue data = { 0, };
|
||
gboolean done = FALSE;
|
||
GstCaps *caps = NULL;
|
||
GstElement *sink = NULL, *tmpelement;
|
||
const gchar *name = gst_structure_get_string (action->structure, "sink-name"),
|
||
*factory_name =
|
||
gst_structure_get_string (action->structure, "sink-factory-name"),
|
||
*caps_str = gst_structure_get_string (action->structure, "sinkpad-caps");
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
if (caps_str) {
|
||
caps = gst_caps_from_string (caps_str);
|
||
|
||
g_assert (caps);
|
||
}
|
||
|
||
it = gst_bin_iterate_recurse (GST_BIN (pipeline));
|
||
while (!done) {
|
||
switch (gst_iterator_next (it, &data)) {
|
||
case GST_ITERATOR_OK:
|
||
tmpelement = g_value_get_object (&data);
|
||
if (_sink_matches_last_sample_specs (tmpelement, name, factory_name,
|
||
caps)) {
|
||
if (sink) {
|
||
if (!gst_object_has_as_ancestor (GST_OBJECT (tmpelement),
|
||
GST_OBJECT (sink))) {
|
||
gchar *tmp = gst_structure_to_string (action->structure);
|
||
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not \"check-last-sample\" as several elements were found "
|
||
"from describing string: '%s' (%s and %s match)", tmp,
|
||
GST_OBJECT_NAME (sink), GST_OBJECT_NAME (tmpelement));
|
||
|
||
g_free (tmp);
|
||
}
|
||
|
||
gst_object_unref (sink);
|
||
}
|
||
|
||
sink = gst_object_ref (tmpelement);
|
||
}
|
||
g_value_reset (&data);
|
||
break;
|
||
case GST_ITERATOR_RESYNC:
|
||
gst_iterator_resync (it);
|
||
g_clear_object (&sink);
|
||
break;
|
||
case GST_ITERATOR_ERROR:
|
||
/* Fallthrough */
|
||
case GST_ITERATOR_DONE:
|
||
done = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
gst_iterator_free (it);
|
||
if (caps)
|
||
gst_caps_unref (caps);
|
||
|
||
if (!sink) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not \"check-last-sample\" as no sink was found from description: '%"
|
||
GST_PTR_FORMAT "'", action->structure);
|
||
|
||
goto error;
|
||
}
|
||
|
||
g_clear_object (&pipeline);
|
||
return _check_last_sample_value (scenario, action, sink);
|
||
|
||
error:
|
||
g_clear_object (&sink);
|
||
g_clear_object (&pipeline);
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
static GstPadProbeReturn
|
||
_check_is_key_unit_cb (GstPad * pad, GstPadProbeInfo * info,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
|
||
GstClockTime target_running_time = GST_CLOCK_TIME_NONE;
|
||
gint count_bufs = 0;
|
||
|
||
gst_validate_action_get_clocktime (scenario, action,
|
||
"running-time", &target_running_time);
|
||
if (GST_IS_EVENT (GST_PAD_PROBE_INFO_DATA (info))) {
|
||
if (gst_video_event_is_force_key_unit (GST_PAD_PROBE_INFO_DATA (info)))
|
||
gst_structure_set (action->structure, "__priv_seen_event", G_TYPE_BOOLEAN,
|
||
TRUE, NULL);
|
||
else if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEGMENT
|
||
&& GST_PAD_DIRECTION (pad) == GST_PAD_SRC) {
|
||
const GstSegment *segment = NULL;
|
||
|
||
gst_event_parse_segment (info->data, &segment);
|
||
gst_structure_set (action->structure, "__priv_segment", GST_TYPE_SEGMENT,
|
||
segment, NULL);
|
||
}
|
||
} else if (GST_IS_BUFFER (GST_PAD_PROBE_INFO_DATA (info))
|
||
&& gst_structure_has_field_typed (action->structure, "__priv_seen_event",
|
||
G_TYPE_BOOLEAN)) {
|
||
GstSegment *segment = NULL;
|
||
|
||
if (GST_CLOCK_TIME_IS_VALID (target_running_time)) {
|
||
GstClockTime running_time;
|
||
|
||
gst_structure_get (action->structure, "__priv_segment", GST_TYPE_SEGMENT,
|
||
&segment, NULL);
|
||
running_time =
|
||
gst_segment_to_running_time (segment, GST_FORMAT_TIME,
|
||
GST_BUFFER_TIMESTAMP (info->data));
|
||
|
||
if (running_time < target_running_time)
|
||
goto done;
|
||
}
|
||
|
||
gst_structure_get_int (action->structure, "__priv_count_bufs", &count_bufs);
|
||
if (GST_BUFFER_FLAG_IS_SET (GST_PAD_PROBE_INFO_BUFFER (info),
|
||
GST_BUFFER_FLAG_DELTA_UNIT)) {
|
||
if (count_bufs >= NOT_KF_AFTER_FORCE_KF_EVT_TOLERANCE) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Did not receive a key frame after requested one, "
|
||
"at running_time %" GST_TIME_FORMAT " (with a %i "
|
||
"frame tolerance)", GST_TIME_ARGS (target_running_time),
|
||
NOT_KF_AFTER_FORCE_KF_EVT_TOLERANCE);
|
||
|
||
gst_validate_action_set_done (action);
|
||
gst_object_unref (scenario);
|
||
return GST_PAD_PROBE_REMOVE;
|
||
}
|
||
|
||
gst_structure_set (action->structure, "__priv_count_bufs", G_TYPE_INT,
|
||
count_bufs++, NULL);
|
||
} else {
|
||
GST_INFO_OBJECT (pad,
|
||
"Properly got keyframe after \"force-keyframe\" event "
|
||
"with running_time %" GST_TIME_FORMAT " (latency %d frame(s))",
|
||
GST_TIME_ARGS (target_running_time), count_bufs);
|
||
|
||
gst_structure_remove_fields (action->structure, "__priv_count_bufs",
|
||
"__priv_segment", "__priv_seen_event", NULL);
|
||
gst_validate_action_set_done (action);
|
||
gst_object_unref (scenario);
|
||
return GST_PAD_PROBE_REMOVE;
|
||
}
|
||
}
|
||
done:
|
||
gst_object_unref (scenario);
|
||
|
||
return GST_PAD_PROBE_OK;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_crank_clock (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstClockTime expected_diff, expected_time;
|
||
GstClockTime prev_time =
|
||
gst_clock_get_time (GST_CLOCK (scenario->priv->clock));
|
||
|
||
if (!gst_test_clock_crank (scenario->priv->clock)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "Cranking clock failed");
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
if (gst_validate_action_get_clocktime (scenario, action,
|
||
"expected-elapsed-time", &expected_diff)) {
|
||
GstClockTime elapsed =
|
||
gst_clock_get_time (GST_CLOCK (scenario->priv->clock)) - prev_time;
|
||
|
||
if (expected_diff != elapsed) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Elapsed time during test clock cranking different than expected,"
|
||
" waited for %" GST_TIME_FORMAT " instead of the expected %"
|
||
GST_TIME_FORMAT, GST_TIME_ARGS (elapsed),
|
||
GST_TIME_ARGS (expected_diff));
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
}
|
||
|
||
if (gst_validate_action_get_clocktime (scenario, action, "expected-time",
|
||
&expected_time)) {
|
||
GstClockTime time = gst_clock_get_time (GST_CLOCK (scenario->priv->clock));
|
||
|
||
if (expected_time != time) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Clock time after cranking different than expected,"
|
||
" got %" GST_TIME_FORMAT " instead of the expected %" GST_TIME_FORMAT,
|
||
GST_TIME_ARGS (time), GST_TIME_ARGS (expected_time));
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
}
|
||
|
||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
}
|
||
|
||
static gboolean
|
||
_execute_request_key_unit (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
guint count = 0;
|
||
gboolean all_headers = FALSE;
|
||
gboolean ret = GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||
GstEvent *event = NULL;
|
||
GstQuery *segment_query;
|
||
GList *targets = NULL, *tmp;
|
||
GstElement *video_encoder = NULL;
|
||
GstPad *pad = NULL, *encoder_srcpad = NULL;
|
||
GstClockTime running_time = GST_CLOCK_TIME_NONE;
|
||
GstSegment segment = { 0, };
|
||
const gchar *direction = gst_structure_get_string (action->structure,
|
||
"direction"), *pad_name, *srcpad_name;
|
||
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
if (gst_structure_get_string (action->structure, "target-element-name")) {
|
||
GstElement *target = _get_target_element (scenario, action);
|
||
if (target == NULL)
|
||
return FALSE;
|
||
|
||
targets = g_list_append (targets, target);
|
||
} else {
|
||
if (!gst_structure_get_string (action->structure,
|
||
"target-element-klass") &&
|
||
!gst_structure_get_string (action->structure,
|
||
"target-element-factory-name")) {
|
||
gst_structure_set (action->structure, "target-element-klass",
|
||
G_TYPE_STRING, "Video/Encoder", NULL);
|
||
}
|
||
|
||
targets = _get_target_elements_by_klass_or_factory_name (scenario, action);
|
||
}
|
||
|
||
if (!targets) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not find any element from action: %" GST_PTR_FORMAT,
|
||
action->structure);
|
||
goto fail;
|
||
}
|
||
|
||
gst_validate_action_get_clocktime (scenario, action,
|
||
"running-time", &running_time);
|
||
gst_structure_get_boolean (action->structure, "all-headers", &all_headers);
|
||
if (!gst_structure_get_uint (action->structure, "count", &count)) {
|
||
gst_structure_get_int (action->structure, "count", (gint *) & count);
|
||
}
|
||
pad_name = gst_structure_get_string (action->structure, "pad");
|
||
srcpad_name = gst_structure_get_string (action->structure, "srcpad");
|
||
if (!srcpad_name)
|
||
srcpad_name = "src";
|
||
|
||
for (tmp = targets; tmp; tmp = tmp->next) {
|
||
video_encoder = tmp->data;
|
||
encoder_srcpad = gst_element_get_static_pad (video_encoder, srcpad_name);
|
||
if (!encoder_srcpad) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "Could not find pad %s",
|
||
srcpad_name);
|
||
|
||
goto fail;
|
||
}
|
||
if (g_strcmp0 (direction, "upstream") == 0) {
|
||
event = gst_video_event_new_upstream_force_key_unit (running_time,
|
||
all_headers, count);
|
||
|
||
pad = gst_element_get_static_pad (video_encoder, srcpad_name);
|
||
if (!pad) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "Could not find pad %s",
|
||
srcpad_name);
|
||
|
||
goto fail;
|
||
}
|
||
GST_ERROR_OBJECT (encoder_srcpad, "Sending RequestKeyUnit event");
|
||
gst_pad_add_probe (encoder_srcpad,
|
||
GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
|
||
(GstPadProbeCallback) _check_is_key_unit_cb,
|
||
gst_validate_action_ref (action),
|
||
(GDestroyNotify) gst_validate_action_unref);
|
||
} else if (g_strcmp0 (direction, "downstream") == 0) {
|
||
GstClockTime timestamp = GST_CLOCK_TIME_NONE,
|
||
stream_time = GST_CLOCK_TIME_NONE;
|
||
|
||
if (!pad_name)
|
||
pad_name = "sink";
|
||
|
||
pad = gst_element_get_static_pad (video_encoder, pad_name);
|
||
if (!pad) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "Could not find pad %s", pad_name);
|
||
|
||
goto fail;
|
||
}
|
||
|
||
gst_validate_action_get_clocktime (scenario, action,
|
||
"timestamp", ×tamp);
|
||
|
||
gst_validate_action_get_clocktime (scenario, action,
|
||
"stream-time", &stream_time);
|
||
|
||
event =
|
||
gst_video_event_new_downstream_force_key_unit (timestamp, stream_time,
|
||
running_time, all_headers, count);
|
||
|
||
gst_pad_add_probe (pad,
|
||
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
|
||
(GstPadProbeCallback) _check_is_key_unit_cb,
|
||
gst_validate_action_ref (action),
|
||
(GDestroyNotify) gst_validate_action_unref);
|
||
} else {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"request keyunit direction %s invalid (should be in"
|
||
" [downstrean, upstream]", direction);
|
||
|
||
goto fail;
|
||
}
|
||
|
||
gst_validate_printf (action, "Sending a \"force key unit\" event %s\n",
|
||
direction);
|
||
|
||
segment_query = gst_query_new_segment (GST_FORMAT_TIME);
|
||
gst_pad_query (encoder_srcpad, segment_query);
|
||
|
||
gst_query_parse_segment (segment_query, &(segment.rate),
|
||
&(segment.format), (gint64 *) & (segment.start),
|
||
(gint64 *) & (segment.stop));
|
||
gst_structure_set (action->structure, "__priv_segment", GST_TYPE_SEGMENT,
|
||
&segment, NULL);
|
||
|
||
gst_pad_add_probe (encoder_srcpad,
|
||
GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
|
||
(GstPadProbeCallback) _check_is_key_unit_cb,
|
||
gst_validate_action_ref (action),
|
||
(GDestroyNotify) gst_validate_action_unref);
|
||
|
||
|
||
if (!gst_pad_send_event (pad, event)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Could not send \"force key unit\" event %s", direction);
|
||
goto fail;
|
||
}
|
||
|
||
gst_clear_object (&pad);
|
||
gst_clear_object (&encoder_srcpad);
|
||
}
|
||
|
||
done:
|
||
g_list_free_full (targets, gst_object_unref);
|
||
gst_clear_object (&pad);
|
||
gst_clear_object (&encoder_srcpad);
|
||
|
||
return ret;
|
||
|
||
fail:
|
||
ret = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
goto done;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_stop (GstValidateScenario * scenario, GstValidateAction * action)
|
||
{
|
||
GstBus *bus;
|
||
GstValidateScenarioPrivate *priv = scenario->priv;
|
||
|
||
DECLARE_AND_GET_PIPELINE (scenario, action);
|
||
|
||
bus = gst_element_get_bus (pipeline);
|
||
|
||
g_signal_emit (scenario, scenario_signals[STOPPING], 0);
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
if (priv->execute_actions_source_id) {
|
||
g_source_remove (priv->execute_actions_source_id);
|
||
priv->execute_actions_source_id = 0;
|
||
}
|
||
if (scenario->priv->actions || scenario->priv->non_blocking_running_actions ||
|
||
scenario->priv->on_addition_actions) {
|
||
guint nb_actions = 0;
|
||
gchar *actions = g_strdup (""), *tmpconcat;
|
||
GList *tmp;
|
||
GList *all_actions = g_list_concat (g_list_concat (scenario->priv->actions,
|
||
scenario->priv->non_blocking_running_actions),
|
||
scenario->priv->on_addition_actions);
|
||
|
||
for (tmp = all_actions; tmp; tmp = tmp->next) {
|
||
GstValidateAction *remaining_action = (GstValidateAction *) tmp->data;
|
||
GstValidateActionType *type;
|
||
|
||
if (remaining_action == action)
|
||
continue;
|
||
|
||
type = _find_action_type (remaining_action->type);
|
||
|
||
tmpconcat = actions;
|
||
|
||
if (type->flags & GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL ||
|
||
remaining_action->priv->state == GST_VALIDATE_EXECUTE_ACTION_OK ||
|
||
remaining_action->priv->optional) {
|
||
gst_validate_action_unref (remaining_action);
|
||
|
||
continue;
|
||
}
|
||
|
||
nb_actions++;
|
||
|
||
actions = g_strdup_printf ("%s\n%*s- `%s` at %s:%d", actions, 20, "",
|
||
remaining_action->type,
|
||
GST_VALIDATE_ACTION_FILENAME (remaining_action),
|
||
GST_VALIDATE_ACTION_LINENO (remaining_action));
|
||
gst_validate_action_unref (remaining_action);
|
||
g_free (tmpconcat);
|
||
}
|
||
g_list_free (all_actions);
|
||
scenario->priv->actions = NULL;
|
||
scenario->priv->non_blocking_running_actions = NULL;
|
||
scenario->priv->on_addition_actions = NULL;
|
||
|
||
|
||
if (nb_actions > 0) {
|
||
GstClockTime position = GST_CLOCK_TIME_NONE;
|
||
|
||
_get_position (scenario, NULL, &position);
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
GST_VALIDATE_REPORT (scenario, SCENARIO_NOT_ENDED,
|
||
"%i actions were not executed: %s (position: %" GST_TIME_FORMAT
|
||
")", nb_actions, actions, GST_TIME_ARGS (position));
|
||
|
||
SCENARIO_LOCK (scenario);
|
||
}
|
||
g_free (actions);
|
||
}
|
||
SCENARIO_UNLOCK (scenario);
|
||
|
||
gst_validate_scenario_check_dropped (scenario);
|
||
gst_bus_post (bus,
|
||
gst_message_new_request_state (GST_OBJECT_CAST (scenario),
|
||
GST_STATE_NULL));
|
||
gst_validate_scenario_stop_http_servers (scenario);
|
||
gst_object_unref (bus);
|
||
gst_object_unref (pipeline);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
_action_set_done (GstValidateAction * action)
|
||
{
|
||
gchar *repeat_message = NULL;
|
||
JsonBuilder *jbuild;
|
||
GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
|
||
|
||
if (scenario == NULL || !action->priv->pending_set_done)
|
||
return G_SOURCE_REMOVE;
|
||
|
||
action->priv->execution_duration =
|
||
gst_util_get_timestamp () - action->priv->execution_time;
|
||
|
||
jbuild = json_builder_new ();
|
||
json_builder_begin_object (jbuild);
|
||
json_builder_set_member_name (jbuild, "type");
|
||
json_builder_add_string_value (jbuild, "action-done");
|
||
json_builder_set_member_name (jbuild, "action-type");
|
||
json_builder_add_string_value (jbuild, action->type);
|
||
json_builder_set_member_name (jbuild, "execution-duration");
|
||
json_builder_add_double_value (jbuild,
|
||
((gdouble) action->priv->execution_duration / GST_SECOND));
|
||
json_builder_end_object (jbuild);
|
||
|
||
gst_validate_send (json_builder_get_root (jbuild));
|
||
g_object_unref (jbuild);
|
||
|
||
action->priv->pending_set_done = FALSE;
|
||
switch (action->priv->state) {
|
||
case GST_VALIDATE_EXECUTE_ACTION_ERROR:
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "Action %s failed", action->type);
|
||
/* FALLTHROUGH */
|
||
case GST_VALIDATE_EXECUTE_ACTION_ASYNC:
|
||
case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS:
|
||
case GST_VALIDATE_EXECUTE_ACTION_NONE:
|
||
case GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED:
|
||
case GST_VALIDATE_EXECUTE_ACTION_OK:
|
||
{
|
||
scenario->priv->actions = g_list_remove (scenario->priv->actions, action);
|
||
|
||
_check_scenario_is_done (scenario);
|
||
|
||
if (!gst_validate_parse_next_action_playback_time (scenario)) {
|
||
gst_validate_error_structure (scenario->priv->actions ? scenario->priv->
|
||
actions->data : NULL,
|
||
"Could not determine next action playback time!");
|
||
}
|
||
|
||
GST_INFO_OBJECT (scenario, "Action %" GST_PTR_FORMAT " is DONE now"
|
||
" executing next", action->structure);
|
||
|
||
break;
|
||
}
|
||
case GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING:
|
||
break;
|
||
}
|
||
|
||
if (GST_VALIDATE_ACTION_N_REPEATS (action))
|
||
repeat_message =
|
||
g_strdup_printf ("[%d/%d]", action->repeat,
|
||
GST_VALIDATE_ACTION_N_REPEATS (action));
|
||
|
||
gst_validate_printf (NULL,
|
||
"%*c⇨ Action `%s` at %s:%d done '%s' %s (duration: %" GST_TIME_FORMAT
|
||
")\n\n", (action->priv->subaction_level * 2) - 1, ' ',
|
||
gst_structure_get_name (action->priv->main_structure),
|
||
GST_VALIDATE_ACTION_FILENAME (action),
|
||
GST_VALIDATE_ACTION_LINENO (action),
|
||
gst_validate_action_return_get_name (action->priv->state),
|
||
repeat_message ? repeat_message : "",
|
||
GST_TIME_ARGS (action->priv->execution_duration));
|
||
g_free (repeat_message);
|
||
|
||
g_signal_emit (scenario, scenario_signals[ACTION_DONE], 0, action);
|
||
if (action->priv->state != GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING)
|
||
/* We took the 'scenario' reference... unreffing it now */
|
||
gst_validate_action_unref (action);
|
||
|
||
action->priv->state = GST_VALIDATE_EXECUTE_ACTION_DONE;
|
||
gst_validate_scenario_execute_next_or_restart_looping (scenario);
|
||
gst_object_unref (scenario);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
/* gst_validate_action_set_done:
|
||
* @action: The action that is done executing
|
||
*
|
||
* Sets @action as "done", meaning that the next action can
|
||
* now be executed.
|
||
*/
|
||
void
|
||
gst_validate_action_set_done (GstValidateAction * action)
|
||
{
|
||
GMainContext *context = action->priv->context;
|
||
GstValidateScenario *scenario = gst_validate_action_get_scenario (action);
|
||
|
||
action->priv->context = NULL;
|
||
if (action->priv->state == GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING) {
|
||
GList *item = NULL;
|
||
|
||
if (scenario) {
|
||
SCENARIO_LOCK (scenario);
|
||
item = g_list_find (scenario->priv->non_blocking_running_actions, action);
|
||
scenario->priv->non_blocking_running_actions =
|
||
g_list_delete_link (scenario->priv->non_blocking_running_actions,
|
||
item);
|
||
SCENARIO_UNLOCK (scenario);
|
||
}
|
||
|
||
if (item)
|
||
gst_validate_action_unref (action);
|
||
}
|
||
|
||
g_assert (!action->priv->pending_set_done);
|
||
action->priv->pending_set_done = TRUE;
|
||
|
||
if (scenario && scenario->priv->wait_message_action == action) {
|
||
gst_validate_action_unref (scenario->priv->wait_message_action);
|
||
scenario->priv->wait_message_action = NULL;
|
||
}
|
||
gst_clear_object (&scenario);
|
||
|
||
g_main_context_invoke_full (action->priv->context,
|
||
G_PRIORITY_DEFAULT_IDLE,
|
||
(GSourceFunc) _action_set_done,
|
||
gst_validate_action_ref (action),
|
||
(GDestroyNotify) gst_validate_action_unref);
|
||
|
||
if (context)
|
||
g_main_context_unref (context);
|
||
}
|
||
|
||
typedef struct
|
||
{
|
||
GstValidateMonitor *monitor;
|
||
GstValidateAction *action;
|
||
} SubPipelineData;
|
||
|
||
static void
|
||
sub_pipeline_data_free (gpointer data)
|
||
{
|
||
SubPipelineData *sub_data = (SubPipelineData *) data;
|
||
|
||
g_clear_object (&sub_data->monitor);
|
||
gst_validate_action_unref (sub_data->action);
|
||
}
|
||
|
||
static void
|
||
sub_pipeline_data_unref (gpointer data)
|
||
{
|
||
g_atomic_rc_box_release_full (data, (GDestroyNotify) sub_pipeline_data_free);
|
||
}
|
||
|
||
static void
|
||
subscenario_done_cb (GstBus * bus, GstMessage * message, gpointer data)
|
||
{
|
||
SubPipelineData *sub_data = (SubPipelineData *) data;
|
||
GstElement *pipeline =
|
||
GST_ELEMENT (gst_validate_monitor_get_target (sub_data->monitor));
|
||
g_assert (pipeline);
|
||
|
||
GstState state;
|
||
|
||
gst_message_parse_request_state (message, &state);
|
||
|
||
if (!GST_IS_VALIDATE_SCENARIO (GST_MESSAGE_SRC (message))
|
||
|| state != GST_STATE_NULL) {
|
||
return;
|
||
}
|
||
|
||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||
gst_validate_action_set_done (sub_data->action);
|
||
|
||
g_signal_handlers_disconnect_by_func (bus, subscenario_done_cb, data);
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_create_sub_pipeline (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
const gchar *pipeline_desc = NULL, *scenario_name = NULL, *name = NULL;
|
||
GError *error = NULL;
|
||
GstPipeline *pipeline;
|
||
GList *scenario_structures = NULL;
|
||
GstValidateRunner *runner = NULL;
|
||
GstValidateExecuteActionReturn res =
|
||
GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
|
||
REPORT_UNLESS ((pipeline_desc = gst_structure_get_string (action->structure,
|
||
"desc")), done,
|
||
"Couldn't find `pipeline` as string in %" GST_PTR_FORMAT,
|
||
action->structure);
|
||
|
||
|
||
REPORT_UNLESS ((pipeline =
|
||
GST_PIPELINE (gst_parse_launch (pipeline_desc, &error))), done,
|
||
"Couldn't create pipeline: %s", error->message);
|
||
|
||
|
||
if ((name = gst_structure_get_string (action->structure, "name"))) {
|
||
gst_object_set_name (GST_OBJECT (pipeline), name);
|
||
}
|
||
|
||
scenario_structures =
|
||
gst_validate_utils_get_structures (action, action->structure, "scenario");
|
||
if (!scenario_structures) {
|
||
scenario_name = gst_structure_get_string (action->structure, "scenario");
|
||
}
|
||
|
||
runner = gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (scenario));
|
||
SubPipelineData *data = g_atomic_rc_box_alloc0 (sizeof (SubPipelineData));
|
||
data->monitor =
|
||
GST_VALIDATE_MONITOR (gst_validate_pipeline_monitor_new_full (pipeline,
|
||
runner, NULL,
|
||
scenario_name ? scenario_name : (name ? name : "unnamed-subscenario"),
|
||
scenario_structures, TRUE));
|
||
data->action = gst_validate_action_ref (action);
|
||
|
||
gboolean monitor_handles_state;
|
||
g_object_get (data->monitor, "handles-states", &monitor_handles_state, NULL);
|
||
if (monitor_handles_state == FALSE) {
|
||
if (gst_element_set_state (GST_ELEMENT (pipeline),
|
||
GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR, "Could not set pipeline to PLAYING");
|
||
|
||
sub_pipeline_data_unref (data);
|
||
goto done;
|
||
}
|
||
}
|
||
|
||
|
||
GstBus *bus = gst_element_get_bus (GST_ELEMENT (pipeline));
|
||
|
||
gst_bus_enable_sync_message_emission (bus);
|
||
|
||
g_signal_connect_data (bus, "sync-message::request-state",
|
||
(GCallback) subscenario_done_cb, data,
|
||
(GClosureNotify) sub_pipeline_data_unref, 0);
|
||
|
||
gst_clear_object (&bus);
|
||
|
||
g_weak_ref_set (&action->priv->sub_pipeline, pipeline);
|
||
res = GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING;
|
||
|
||
done:
|
||
g_clear_error (&error);
|
||
g_clear_object (&runner);
|
||
|
||
return res;
|
||
}
|
||
|
||
static GstFlowReturn
|
||
forward_appsink_to_appsrc_new_sample (GstAppSink * appsink, gpointer user_data)
|
||
{
|
||
GstAppSrc *appsrc = GST_APP_SRC (user_data);
|
||
|
||
GstSample *sample = gst_app_sink_pull_sample (appsink);
|
||
if (!sample) {
|
||
return GST_FLOW_ERROR;
|
||
}
|
||
|
||
GstFlowReturn ret = gst_app_src_push_sample (appsrc, sample);
|
||
gst_sample_unref (sample);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static void
|
||
forward_appsink_to_appsrc_eos (GstAppSink * appsink, gpointer user_data)
|
||
{
|
||
GstAppSrc *appsrc = GST_APP_SRC (user_data);
|
||
|
||
gst_app_src_end_of_stream (GST_APP_SRC (appsrc));
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_forward_appsink_to_appsrc (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
gboolean forward_eos = TRUE;
|
||
gpointer forward_eos_func = forward_appsink_to_appsrc_eos;
|
||
GstElement *sink = NULL, *src = NULL;
|
||
GstElement *sink_pipeline = NULL, *src_pipeline = NULL;
|
||
gchar **src_pipeline_element = NULL, **sink_pipeline_element = NULL;
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
|
||
const gchar *sink_name = gst_structure_get_string (action->structure, "sink");
|
||
const gchar *src_name = gst_structure_get_string (action->structure, "src");
|
||
|
||
sink_pipeline_element = g_strsplit (sink_name, "/", 2);
|
||
if (sink_pipeline_element[1]) {
|
||
sink_pipeline =
|
||
gst_validate_scenario_get_sub_pipeline (scenario,
|
||
sink_pipeline_element[0]);
|
||
|
||
REPORT_UNLESS (sink_pipeline, done, "Could not find subpipeline `%s`",
|
||
sink_pipeline_element[0]);
|
||
} else {
|
||
sink_pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
}
|
||
|
||
src_pipeline_element = g_strsplit (src_name, "/", 2);
|
||
if (src_pipeline_element[1]) {
|
||
src_pipeline =
|
||
gst_validate_scenario_get_sub_pipeline (scenario,
|
||
src_pipeline_element[0]);
|
||
|
||
REPORT_UNLESS (sink_pipeline, done, "Could not find subpipeline `%s`",
|
||
src_pipeline_element[0]);
|
||
} else {
|
||
src_pipeline = gst_validate_scenario_get_pipeline (scenario);
|
||
}
|
||
|
||
REPORT_UNLESS (((sink =
|
||
gst_bin_get_by_name (GST_BIN (sink_pipeline),
|
||
sink_pipeline_element[1] ? sink_pipeline_element[1] :
|
||
sink_name))
|
||
&& GST_IS_APP_SINK (sink)), done,
|
||
"Could not find appsink '%s' (%" GST_PTR_FORMAT ") in %" GST_PTR_FORMAT,
|
||
sink_name, sink, sink_pipeline);
|
||
REPORT_UNLESS (((src =
|
||
gst_bin_get_by_name (GST_BIN (src_pipeline),
|
||
src_pipeline_element[1] ? src_pipeline_element[1] : src_name))
|
||
&& GST_IS_APP_SRC (src)), done,
|
||
"Could not find appsrc '%s' (%" GST_PTR_FORMAT ") in %" GST_PTR_FORMAT,
|
||
src_name, src, src_pipeline);
|
||
|
||
if (gst_structure_get_boolean (action->structure, "forward-eos",
|
||
&forward_eos) && !forward_eos) {
|
||
forward_eos_func = NULL;
|
||
}
|
||
|
||
GstAppSinkCallbacks callbacks = {
|
||
.eos = forward_eos_func,
|
||
.new_preroll = NULL,
|
||
.new_sample = forward_appsink_to_appsrc_new_sample
|
||
};
|
||
|
||
gst_app_sink_set_callbacks (GST_APP_SINK (sink), &callbacks,
|
||
gst_object_ref (src), gst_object_unref);
|
||
|
||
done:
|
||
if (src_pipeline_element)
|
||
g_strfreev (src_pipeline_element);
|
||
if (sink_pipeline_element)
|
||
g_strfreev (sink_pipeline_element);
|
||
gst_clear_object (&src_pipeline);
|
||
gst_clear_object (&sink_pipeline);
|
||
gst_clear_object (&src);
|
||
gst_clear_object (&sink);
|
||
|
||
return res;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_http_request (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GError *error = NULL;
|
||
GstValidateExecuteActionReturn ret = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
|
||
if (!run_http_request (action->structure, &error)) {
|
||
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||
SCENARIO_ACTION_EXECUTION_ERROR,
|
||
"Failed to execute HTTP request: %s",
|
||
error ? error->message : "Unknown error");
|
||
g_clear_error (&error);
|
||
ret = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static GstValidateExecuteActionReturn
|
||
_execute_start_http_server (GstValidateScenario * scenario,
|
||
GstValidateAction * action)
|
||
{
|
||
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||
const gchar *server_path, *working_dir;
|
||
GError *err = NULL;
|
||
HTTPServer server = { 0 };
|
||
GInputStream *stdout_stream = NULL;
|
||
GSubprocess *subprocess = NULL;
|
||
gint port = 0;
|
||
|
||
server_path = g_getenv ("GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH");
|
||
REPORT_UNLESS (server_path, done,
|
||
"GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH not set");
|
||
|
||
REPORT_UNLESS (g_file_test (server_path,
|
||
G_FILE_TEST_IS_REGULAR), done,
|
||
"HTTP server script not found at: %s", server_path);
|
||
|
||
working_dir =
|
||
gst_structure_get_string (action->structure, "working-directory");
|
||
REPORT_UNLESS (working_dir, done, "working-directory not specified");
|
||
REPORT_UNLESS (g_file_test (working_dir,
|
||
G_FILE_TEST_IS_DIR), done,
|
||
"working-directory '%s' doesn't exist", working_dir);
|
||
|
||
gchar const *argv[3] = { server_path, "0", NULL };
|
||
gboolean no_pipe = FALSE;
|
||
gst_structure_get_boolean (action->structure, "no-pipe", &no_pipe);
|
||
GSubprocessLauncher *launcher =
|
||
g_subprocess_launcher_new (no_pipe ? G_SUBPROCESS_FLAGS_STDOUT_PIPE :
|
||
G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE);
|
||
g_subprocess_launcher_set_cwd (launcher, working_dir);
|
||
subprocess =
|
||
g_subprocess_launcher_spawnv (launcher, (const gchar * const *) argv,
|
||
&err);
|
||
g_object_unref (launcher);
|
||
|
||
REPORT_UNLESS (subprocess, done,
|
||
"Failed to start HTTP server: %s", err->message);
|
||
|
||
stdout_stream = g_subprocess_get_stdout_pipe (subprocess);
|
||
GDataInputStream *data_stream = g_data_input_stream_new (stdout_stream);
|
||
gchar *line = g_data_input_stream_read_line (data_stream, NULL, NULL, &err);
|
||
g_object_unref (data_stream);
|
||
|
||
REPORT_UNLESS (err == NULL, done, "Failed to read server output: %s",
|
||
err->message);
|
||
REPORT_UNLESS (sscanf (line, "PORT: %d", &port) == 1, done,
|
||
"Failed to parse port number from server output: %s", line);
|
||
g_free (line);
|
||
|
||
server.port = port;
|
||
server.subprocess = subprocess;
|
||
|
||
if (!scenario->priv->http_servers)
|
||
scenario->priv->http_servers =
|
||
g_array_new (FALSE, FALSE, sizeof (HTTPServer));
|
||
|
||
g_array_append_val (scenario->priv->http_servers, server);
|
||
|
||
gint i = 1;
|
||
gchar *port_varname = g_strdup ("http_server_port");
|
||
while (gst_structure_has_field (scenario->priv->vars, port_varname)) {
|
||
g_free (port_varname);
|
||
port_varname = g_strdup_printf ("http_server_port_%d", i++);
|
||
}
|
||
|
||
gst_structure_set (scenario->priv->vars, port_varname,
|
||
G_TYPE_INT, port, NULL);
|
||
g_free (port_varname);
|
||
|
||
done:
|
||
if (subprocess && !server.subprocess) {
|
||
g_object_unref (subprocess);
|
||
}
|
||
g_clear_error (&err);
|
||
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* gst_validate_action_get_scenario:
|
||
* @action: The action for which to retrieve the scenario
|
||
*
|
||
* Retrieve the scenario from which @action is executed.
|
||
*
|
||
* Returns: (transfer full) (nullable): The scenario from which the action is being executed.
|
||
*/
|
||
GstValidateScenario *
|
||
gst_validate_action_get_scenario (GstValidateAction * action)
|
||
{
|
||
return g_weak_ref_get (&action->priv->scenario);
|
||
}
|
||
|
||
/**
|
||
* gst_validate_register_action_type:
|
||
* @type_name: The name of the new action type to add
|
||
* @implementer_namespace: The namespace of the implementer of the action type.
|
||
* That should always be the name of the GstPlugin as
|
||
* retrieved with #gst_plugin_get_name when the action type
|
||
* is registered inside a plugin.
|
||
* @function: (scope notified): The function to be called to execute the action
|
||
* @parameters: (allow-none) (array zero-terminated=1) (element-type GstValidateActionParameter): The #GstValidateActionParameter usable as parameter of the type
|
||
* @description: A description of the new type
|
||
* @flags: The #GstValidateActionTypeFlags to set on the new action type
|
||
*
|
||
* Register a new action type to the action type system. If the action type already
|
||
* exists, it will be overridden by the new definition
|
||
*
|
||
* Returns: (transfer none): The newly created action type or the already registered action type
|
||
* if it had a higher rank
|
||
*/
|
||
GstValidateActionType *
|
||
gst_validate_register_action_type (const gchar * type_name,
|
||
const gchar * implementer_namespace,
|
||
GstValidateExecuteAction function,
|
||
GstValidateActionParameter * parameters,
|
||
const gchar * description, GstValidateActionTypeFlags flags)
|
||
{
|
||
GstValidateActionType *type = gst_validate_register_action_type_dynamic (NULL,
|
||
type_name, GST_RANK_NONE, function, parameters, description,
|
||
flags);
|
||
|
||
g_free (type->implementer_namespace);
|
||
type->implementer_namespace = g_strdup (implementer_namespace);
|
||
|
||
return type;
|
||
}
|
||
|
||
static void
|
||
_free_action_types (GList * _action_types)
|
||
{
|
||
g_list_free_full (_action_types, (GDestroyNotify) gst_mini_object_unref);
|
||
}
|
||
|
||
/**
|
||
* gst_validate_register_action_type_dynamic:
|
||
* @plugin: (allow-none): The #GstPlugin that register the action type,
|
||
* or NULL for a static element.
|
||
* @rank: The ranking of that implementation of the action type called
|
||
* @type_name. If an action type has been registered with the same
|
||
* name with a higher rank, the new implementation will not be used,
|
||
* and the already registered action type is returned.
|
||
* If the already registered implementation has a lower rank, the
|
||
* new implementation will be used and returned.
|
||
* @type_name: The name of the new action type to add
|
||
* @function: (scope notified): The function to be called to execute the action
|
||
* @parameters: (allow-none) (array zero-terminated=1) (element-type GstValidateActionParameter): The #GstValidateActionParameter usable as parameter of the type
|
||
* @description: A description of the new type
|
||
* @flags: The #GstValidateActionTypeFlags to be set on the new action type
|
||
*
|
||
* Returns: (transfer none): The newly created action type or the already registered action type
|
||
* if it had a higher rank
|
||
*/
|
||
GstValidateActionType *
|
||
gst_validate_register_action_type_dynamic (GstPlugin * plugin,
|
||
const gchar * type_name, GstRank rank,
|
||
GstValidateExecuteAction function, GstValidateActionParameter * parameters,
|
||
const gchar * description, GstValidateActionTypeFlags flags)
|
||
{
|
||
GstValidateActionType *tmptype;
|
||
GstValidateActionType *type = gst_validate_action_type_new ();
|
||
gboolean is_config = IS_CONFIG_ACTION_TYPE (flags);
|
||
gint n_params = is_config ? 0 : 2;
|
||
|
||
if (parameters) {
|
||
for (n_params = 0; parameters[n_params].name != NULL; n_params++);
|
||
n_params += 1;
|
||
}
|
||
|
||
if (n_params) {
|
||
type->parameters = g_new0 (GstValidateActionParameter, n_params);
|
||
}
|
||
|
||
if (parameters) {
|
||
memcpy (type->parameters, parameters,
|
||
sizeof (GstValidateActionParameter) * (n_params));
|
||
}
|
||
|
||
type->prepare = gst_validate_action_default_prepare_func;
|
||
type->execute = function;
|
||
type->name = g_strdup (type_name);
|
||
if (plugin)
|
||
type->implementer_namespace = g_strdup (gst_plugin_get_name (plugin));
|
||
else
|
||
type->implementer_namespace = g_strdup ("none");
|
||
|
||
type->description = g_strdup (description);
|
||
type->flags = flags;
|
||
type->rank = rank;
|
||
|
||
if ((tmptype = _find_action_type (type_name))) {
|
||
if (tmptype->rank <= rank) {
|
||
action_types = g_list_remove (action_types, tmptype);
|
||
type->overriden_type = tmptype;
|
||
} else {
|
||
gst_mini_object_unref (GST_MINI_OBJECT (type));
|
||
|
||
type = tmptype;
|
||
}
|
||
}
|
||
|
||
if (type != tmptype)
|
||
action_types = g_list_append (action_types, type);
|
||
|
||
if (plugin) {
|
||
GList *plugin_action_types = g_object_steal_data (G_OBJECT (plugin),
|
||
"GstValidatePluginActionTypes");
|
||
|
||
plugin_action_types = g_list_prepend (plugin_action_types,
|
||
gst_mini_object_ref (GST_MINI_OBJECT (type)));
|
||
|
||
g_object_set_data_full (G_OBJECT (plugin), "GstValidatePluginActionTypes",
|
||
plugin_action_types, (GDestroyNotify) _free_action_types);
|
||
}
|
||
|
||
return type;
|
||
}
|
||
|
||
GstValidateActionType *
|
||
gst_validate_get_action_type (const gchar * type_name)
|
||
{
|
||
GstValidateActionType *type = _find_action_type (type_name);
|
||
|
||
if (type)
|
||
return
|
||
GST_VALIDATE_ACTION_TYPE (gst_mini_object_ref (GST_MINI_OBJECT (type)));
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static GList *
|
||
gst_validate_list_action_types (void)
|
||
{
|
||
return action_types;
|
||
}
|
||
|
||
/**
|
||
* gst_validate_print_action_types:
|
||
* @wanted_types: (array length=num_wanted_types): (optional): List of types to be printed
|
||
* @num_wanted_types: Length of @wanted_types
|
||
*
|
||
* Prints the action types details wanted in @wanted_types
|
||
*
|
||
* Returns: True if all types could be printed
|
||
*/
|
||
gboolean
|
||
gst_validate_print_action_types (const gchar ** wanted_types,
|
||
gint num_wanted_types)
|
||
{
|
||
GList *tmp;
|
||
gint nfound = 0;
|
||
gboolean print_all = (num_wanted_types == 1
|
||
&& !g_strcmp0 (wanted_types[0], "all"));
|
||
|
||
if (print_all)
|
||
gst_validate_printf (NULL, "# GstValidate action types");
|
||
|
||
for (tmp = gst_validate_list_action_types (); tmp; tmp = tmp->next) {
|
||
GstValidateActionType *atype = (GstValidateActionType *) tmp->data;
|
||
gboolean print = print_all;
|
||
|
||
if (num_wanted_types) {
|
||
gint n;
|
||
|
||
for (n = 0; n < num_wanted_types; n++) {
|
||
if (g_strcmp0 (atype->name, wanted_types[n]) == 0 ||
|
||
g_strcmp0 (atype->implementer_namespace, wanted_types[n]) == 0) {
|
||
nfound++;
|
||
print = TRUE;
|
||
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
print = TRUE;
|
||
}
|
||
|
||
if (print && num_wanted_types) {
|
||
gst_validate_printf (atype, "\n");
|
||
} else if (print) {
|
||
gchar *desc =
|
||
g_regex_replace (newline_regex, atype->description, -1, 0, "\n ",
|
||
0,
|
||
NULL);
|
||
|
||
gst_validate_printf (NULL, "\n%s: %s:\n %s\n",
|
||
atype->implementer_namespace, atype->name, desc);
|
||
g_free (desc);
|
||
}
|
||
}
|
||
|
||
if (!print_all && num_wanted_types && num_wanted_types > nfound) {
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* gst_validate_scenario_get_actions:
|
||
* @scenario: The scenario to retrieve remaining actions for
|
||
*
|
||
* Get remaining actions from @scenario.
|
||
*
|
||
* Returns: (transfer full) (element-type GstValidateAction): A list of #GstValidateAction.
|
||
*/
|
||
GList *
|
||
gst_validate_scenario_get_actions (GstValidateScenario * scenario)
|
||
{
|
||
GList *ret;
|
||
gboolean main_context_acquired;
|
||
|
||
main_context_acquired = g_main_context_acquire (g_main_context_default ());
|
||
g_return_val_if_fail (main_context_acquired, NULL);
|
||
|
||
ret = g_list_copy_deep (scenario->priv->actions,
|
||
(GCopyFunc) gst_validate_action_ref, NULL);
|
||
|
||
g_main_context_release (g_main_context_default ());
|
||
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* gst_validate_scenario_get_target_state:
|
||
* @scenario: The scenario to retrieve the current target state for
|
||
*
|
||
* Get current target state from @scenario.
|
||
*
|
||
* Returns: Current target state.
|
||
*/
|
||
GstState
|
||
gst_validate_scenario_get_target_state (GstValidateScenario * scenario)
|
||
{
|
||
return scenario->priv->target_state;
|
||
}
|
||
|
||
void
|
||
init_scenarios (void)
|
||
{
|
||
GList *tmp;
|
||
|
||
register_action_types ();
|
||
|
||
for (tmp = gst_validate_plugin_get_config (NULL); tmp; tmp = tmp->next) {
|
||
const gchar *action_typename;
|
||
GstStructure *plug_conf = (GstStructure *) tmp->data;
|
||
|
||
if ((action_typename = gst_structure_get_string (plug_conf, "action"))) {
|
||
GstValidateAction *action;
|
||
GstValidateActionType *atype = _find_action_type (action_typename);
|
||
|
||
if (!atype) {
|
||
gst_validate_error_structure (plug_conf,
|
||
"[CONFIG ERROR] Action type %s not found", action_typename);
|
||
|
||
continue;
|
||
}
|
||
|
||
|
||
if (atype->flags & GST_VALIDATE_ACTION_TYPE_HANDLED_IN_CONFIG) {
|
||
GST_INFO ("Action type %s from configuration files"
|
||
" is handled.", action_typename);
|
||
continue;
|
||
}
|
||
|
||
if (!(atype->flags & GST_VALIDATE_ACTION_TYPE_CONFIG) &&
|
||
!(_action_type_has_parameter (atype, "as-config"))) {
|
||
gst_validate_error_structure (plug_conf,
|
||
"[CONFIG ERROR] Action '%s' is not a config action",
|
||
action_typename);
|
||
|
||
continue;
|
||
}
|
||
|
||
gst_structure_set (plug_conf, "as-config", G_TYPE_BOOLEAN, TRUE, NULL);
|
||
gst_structure_set_name (plug_conf, action_typename);
|
||
|
||
action = gst_validate_action_new (NULL, atype, plug_conf, FALSE);
|
||
gst_validate_action_unref (action);
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
register_action_types (void)
|
||
{
|
||
GstValidateActionType *type;
|
||
GST_DEBUG_CATEGORY_INIT (gst_validate_scenario_debug, "gstvalidatescenario",
|
||
GST_DEBUG_FG_YELLOW, "Gst validate scenarios");
|
||
|
||
_gst_validate_action_type = gst_validate_action_get_type ();
|
||
_gst_validate_action_type_type = gst_validate_action_type_get_type ();
|
||
|
||
GResource *resource = validate_get_resource ();
|
||
g_assert (resource);
|
||
GBytes *meta_config_doc =
|
||
g_resource_lookup_data (resource, "/validate/doc/meta-configs.md",
|
||
G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
|
||
GBytes *meta_expected_issues_doc =
|
||
g_resource_lookup_data (resource, "/validate/doc/meta-expected-issues.md",
|
||
G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
|
||
GBytes *meta_features_rank_doc =
|
||
g_resource_lookup_data (resource, "/validate/doc/meta-features-rank.md",
|
||
G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
|
||
|
||
/* *INDENT-OFF* */
|
||
REGISTER_ACTION_TYPE ("meta", NULL,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "summary",
|
||
.description = "A human readable summary of what the test/scenario does",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
.possible_variables = NULL,
|
||
.def = "'Nothing'"},
|
||
{
|
||
.name = "is-config",
|
||
.description = "Whether the scenario is a config only scenario",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.possible_variables = NULL,
|
||
.def = "false"
|
||
},
|
||
{
|
||
.name = "handles-states",
|
||
.description = "Whether the scenario handles pipeline state changes from the beginning\n"
|
||
"in that case the application should not set the state of the pipeline to anything\n"
|
||
"and the scenario action will be executed from the beginning",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.possible_variables = NULL,
|
||
.def = "false"},
|
||
{
|
||
.name = "seek",
|
||
.description = "Whether the scenario executes seek actions or not",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.possible_variables = NULL,
|
||
.def = "false"
|
||
},
|
||
{
|
||
.name = "reverse-playback",
|
||
.description = "Whether the scenario plays the stream backward",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.possible_variables = NULL,
|
||
.def = "false"
|
||
},
|
||
{
|
||
.name = "need-clock-sync",
|
||
.description = "Whether the scenario needs the execution to be synchronized with the pipeline's\n"
|
||
"clock. Letting the user know if it can be used with a 'fakesink sync=false' sink",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.possible_variables = NULL,
|
||
.def = "true if some action requires a playback-time false otherwise"
|
||
},
|
||
{
|
||
.name = "min-media-duration",
|
||
.description = "Lets the user know the minimum duration of the stream for the scenario\n"
|
||
"to be usable",
|
||
.mandatory = FALSE,
|
||
.types = "double",
|
||
.possible_variables = NULL,
|
||
.def = "0.0"
|
||
},
|
||
{
|
||
.name = "min-audio-track",
|
||
.description = "Lets the user know the minimum number of audio tracks the stream needs to contain\n"
|
||
"for the scenario to be usable",
|
||
.mandatory = FALSE,
|
||
.types = "int",
|
||
.possible_variables = NULL,
|
||
.def = "0"
|
||
},
|
||
{
|
||
.name = "min-video-track",
|
||
.description = "Lets the user know the minimum number of video tracks the stream needs to contain\n"
|
||
"for the scenario to be usable",
|
||
.mandatory = FALSE,
|
||
.types = "int",
|
||
.possible_variables = NULL,
|
||
.def = "0"
|
||
},
|
||
{
|
||
.name = "duration",
|
||
.description = "Lets the user know the time the scenario needs to be fully executed",
|
||
.mandatory = FALSE,
|
||
.types = "double, int",
|
||
.possible_variables = NULL,
|
||
.def = "infinite (GST_CLOCK_TIME_NONE)"
|
||
},
|
||
{
|
||
.name = "pipeline-name",
|
||
.description = "The name of the GstPipeline on which the scenario should be executed.\n"
|
||
"It has the same effect as setting the pipeline using pipeline_name->scenario_name.",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
.possible_variables = NULL,
|
||
.def = "NULL"
|
||
},
|
||
{
|
||
.name = "max-latency",
|
||
.description = "The maximum latency in nanoseconds allowed for this pipeline.\n"
|
||
"It can be overridden using core configuration, like for example by defining the "
|
||
"env variable GST_VALIDATE_CONFIG=core,max-latency=33000000",
|
||
.mandatory = FALSE,
|
||
.types = "double, int",
|
||
.possible_variables = NULL,
|
||
.def = "infinite (GST_CLOCK_TIME_NONE)"
|
||
},
|
||
{
|
||
.name = "max-dropped",
|
||
.description = "The maximum number of buffers which can be dropped by the QoS system allowed for this pipeline.\n"
|
||
"It can be overridden using core configuration, like for example by defining the "
|
||
"env variable GST_VALIDATE_CONFIG=core,max-dropped=100",
|
||
.mandatory = FALSE,
|
||
.types = "int",
|
||
.possible_variables = NULL,
|
||
.def = "infinite (-1)"
|
||
},
|
||
{
|
||
.name = "ignore-eos",
|
||
.description = "Ignore EOS and keep executing the scenario when it happens.\n By default "
|
||
"a 'stop' action is generated one EOS",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.possible_variables = NULL,
|
||
.def = "false"
|
||
},
|
||
{
|
||
.name = "allow-errors",
|
||
.description = "Ignore error messages and keep executing the\n"
|
||
"scenario when it happens. By default a 'stop' action is generated on ERROR messages",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.possible_variables = NULL,
|
||
.def = "false"
|
||
},
|
||
{
|
||
.name = "base-time",
|
||
.description = "The `base-time` fields lets you set the Pipeline base-time as defined in [gst_element_set_base_time](gst_element_set_base_time).\n",
|
||
.mandatory = FALSE,
|
||
.types = "double or string (GstClockTime)",
|
||
.possible_variables = NULL,
|
||
.def = "None"
|
||
},
|
||
{
|
||
.name = "start-time",
|
||
.description = "The `start-time` fields lets you set the Pipeline start-time as defined in [gst_element_set_start_time](gst_element_set_start_time).\n",
|
||
.mandatory = FALSE,
|
||
.types = "double or string (GstClockTime)",
|
||
.possible_variables = NULL,
|
||
.def = "None"
|
||
},
|
||
{
|
||
.name = "use-system-clock",
|
||
.description = "The `use-system-clock` fields lets you force the Pipeline to use the\n"
|
||
"[`GstSystemClock`](GstSystemClock)",
|
||
.mandatory = FALSE,
|
||
.types = "bool",
|
||
.possible_variables = NULL,
|
||
.def = "false"
|
||
},
|
||
{
|
||
.name="configs",
|
||
.description=g_bytes_get_data (meta_config_doc, NULL),
|
||
.mandatory = FALSE,
|
||
.types = "{GstStructure as string}",
|
||
.possible_variables = NULL,
|
||
.def = "{}"
|
||
},
|
||
{
|
||
.name="expected-issues",
|
||
.description=g_bytes_get_data (meta_expected_issues_doc, NULL),
|
||
.mandatory = FALSE,
|
||
.types = "{GstStructure as string}",
|
||
.possible_variables = NULL,
|
||
.def = "{}"
|
||
},
|
||
{
|
||
.name="features-rank",
|
||
.description=g_bytes_get_data (meta_features_rank_doc, NULL),
|
||
.mandatory = FALSE,
|
||
.types = "bool",
|
||
.possible_variables = NULL,
|
||
.def = "false"
|
||
},
|
||
{
|
||
.name="monitor-all-pipelines",
|
||
.description="This should only be used in `.validatetest` files, and allows forcing to monitor "
|
||
"all pipelines instead of only the one the tools wanted to monitor, for example to "
|
||
"use `validateflow` on auxilary pipelines",
|
||
.mandatory = FALSE,
|
||
.types = "bool",
|
||
.possible_variables = NULL,
|
||
.def = "false"
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Scenario metadata.\n\nNOTE: it used to be called \"description\"",
|
||
GST_VALIDATE_ACTION_TYPE_CONFIG);
|
||
g_bytes_unref (meta_config_doc);
|
||
g_bytes_unref (meta_expected_issues_doc);
|
||
g_bytes_unref (meta_features_rank_doc);
|
||
|
||
REGISTER_ACTION_TYPE ("seek", _execute_seek,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "start",
|
||
.description = "The starting value of the seek",
|
||
.mandatory = TRUE,
|
||
.types = "double or string (GstClockTime)",
|
||
.possible_variables =
|
||
"`position`: The current position in the stream\n"
|
||
"`duration`: The duration of the stream",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "flags",
|
||
.description = "The GstSeekFlags to use",
|
||
.mandatory = TRUE,
|
||
.types = "string describing the GstSeekFlags to set",
|
||
NULL,
|
||
},
|
||
{
|
||
.name = "rate",
|
||
.description = "The rate value of the seek",
|
||
.mandatory = FALSE,
|
||
.types = "double",
|
||
.possible_variables = NULL,
|
||
.def = "1.0"
|
||
},
|
||
{
|
||
.name = "start_type",
|
||
.description = "The GstSeekType to use for the start of the seek, in:\n"
|
||
" [none, set, end]",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
.possible_variables = NULL,
|
||
.def = "set"
|
||
},
|
||
{
|
||
.name = "stop_type",
|
||
.description = "The GstSeekType to use for the stop of the seek, in:\n"
|
||
" [none, set, end]",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
.possible_variables = NULL,
|
||
.def = "set"
|
||
},
|
||
{
|
||
.name = "stop",
|
||
.description = "The stop value of the seek",
|
||
.mandatory = FALSE,
|
||
.types = "double or string (GstClockTime)",
|
||
.possible_variables =
|
||
"`position`: The current position in the stream\n"
|
||
"`duration`: The duration of the stream",
|
||
.def ="GST_CLOCK_TIME_NONE",
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Seeks into the stream. This is an example of a seek happening when the stream reaches 5 seconds\n"
|
||
"or 1 eighth of its duration and seeks to 10s or 2 eighths of its duration:\n"
|
||
"\n\n```\n"
|
||
" seek, playback-time=\"min(5.0, (duration/8))\", start=\"min(10, 2*(duration/8))\", flags=accurate+flush"
|
||
"\n```\n",
|
||
GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK
|
||
);
|
||
|
||
REGISTER_ACTION_TYPE ("pause", _execute_pause,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "duration",
|
||
.description = "The duration during which the stream will be paused",
|
||
.mandatory = FALSE,
|
||
.types = "double or string (GstClockTime)",
|
||
.possible_variables = NULL,
|
||
.def = "0.0",
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Sets pipeline to PAUSED. You can add a 'duration'\n"
|
||
"parameter so the pipeline goes back to playing after that duration\n"
|
||
"(in second)",
|
||
GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK | GST_VALIDATE_ACTION_TYPE_ASYNC);
|
||
|
||
REGISTER_ACTION_TYPE ("play", _execute_play, NULL,
|
||
"Sets the pipeline state to PLAYING", GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("stop", _execute_stop, NULL,
|
||
"Stops the execution of the scenario. It will post a 'request-state'"
|
||
" message on the bus with NULL as a requested state"
|
||
" and the application is responsible for stopping itself."
|
||
" If you override that action type, make sure to link up.",
|
||
GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL);
|
||
|
||
REGISTER_ACTION_TYPE ("eos", _execute_eos, NULL,
|
||
"Sends an EOS event to the pipeline",
|
||
GST_VALIDATE_ACTION_TYPE_NO_EXECUTION_NOT_FATAL);
|
||
|
||
REGISTER_ACTION_TYPE ("select-streams", _execute_select_streams,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "indexes",
|
||
.description = "Indexes of the streams in the StreamCollection to select",
|
||
.mandatory = TRUE,
|
||
.types = "[int]",
|
||
.possible_variables = NULL,
|
||
},
|
||
{
|
||
.name = "n-calls",
|
||
.description = "Number of times the `select-stream` event should be sent to the pipeline\n"
|
||
" - `0` means 0 or more"
|
||
" - `-1` means at least once"
|
||
" - Other numbers are exact number of calls",
|
||
.mandatory = FALSE,
|
||
.types = "int",
|
||
.def = "1",
|
||
.possible_variables = NULL,
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Select the stream on next `GST_STREAM_COLLECTION` message on the bus.",
|
||
GST_VALIDATE_ACTION_TYPE_NON_BLOCKING);
|
||
|
||
REGISTER_ACTION_TYPE ("switch-track", _execute_switch_track,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "type",
|
||
.description = "Selects which track type to change (can be 'audio', 'video',"
|
||
" or 'text').",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
.possible_variables = NULL,
|
||
.def = "audio",
|
||
},
|
||
{
|
||
.name = "index",
|
||
.description = "Selects which track of this type to use: it can be either a number,\n"
|
||
"which will be the Nth track of the given type, or a number with a '+' or\n"
|
||
"'-' prefix, which means a relative change (eg, '+1' means 'next track',\n"
|
||
"'-1' means 'previous track')",
|
||
.mandatory = FALSE,
|
||
.types = "string: to switch track relatively\n"
|
||
"int: To use the actual index to use",
|
||
.possible_variables = NULL,
|
||
.def = "+1",
|
||
},
|
||
{NULL}
|
||
}),
|
||
"The 'switch-track' command can be used to switch tracks."
|
||
, GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("wait", _execute_wait,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "duration",
|
||
.description = "the duration while no other action will be executed",
|
||
.mandatory = FALSE,
|
||
.types = "double or string (GstClockTime)",
|
||
NULL},
|
||
{
|
||
.name = "target-element-name",
|
||
.description = "The name of the GstElement to wait @signal-name on.",
|
||
.mandatory = FALSE,
|
||
.types = "string"
|
||
},
|
||
{
|
||
.name = "target-element-factory-name",
|
||
.description = "The name factory for which to wait @signal-name on",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "signal-name",
|
||
.description = "The name of the signal to wait for on @target-element-name."
|
||
" To ensure that the signal is executed without blocking while waiting for it"
|
||
" you can set the field 'non-blocking=true'.",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "property-name",
|
||
.description = "The name of the property to wait for value to be set to what is specified by @property-value.",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "property-value",
|
||
.description = "The value of the property to be waiting."
|
||
"\n Example: "
|
||
"\n `wait, property-name=current-uri, property-value=file:///some/value.mp4, target-element-name=uridecodebin`",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "non-blocking",
|
||
.description = "**Only for signals**."
|
||
"Make the action non blocking meaning that next actions will be\n"
|
||
"executed without waiting for the signal to be emitted.",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "message-type",
|
||
.description = "The name of the message type to wait for (on @target-element-name"
|
||
" if specified)",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "expected-values",
|
||
.description = "Expected values in the message structure (valid only when "
|
||
"`message-type`). Example: "
|
||
"wait, on-client=true, message-type=buffering, expected-values=[values, buffer-percent=100]",
|
||
.mandatory = FALSE,
|
||
.types = "structure",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "on-clock",
|
||
.description = "Wait until the test clock gets a new pending entry.\n"
|
||
"See #gst_test_clock_wait_for_next_pending_id.",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "subpipeline-done",
|
||
.description = "Waits that the subpipeline with that name is done",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "check",
|
||
.description = "The check action to execute when non blocking signal is received",
|
||
.mandatory = FALSE,
|
||
.types = "structure",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "deep-property-path",
|
||
.description = "The property to wait to be set on the object inside the pipeline that matches"
|
||
" the path defined as:\n\n"
|
||
"```\n"
|
||
"element-name.padname::property-name=new-value\n"
|
||
"```\n\n"
|
||
"> NOTE: `.padname` is not needed if setting a property on an element\n\n",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
|
||
{NULL}
|
||
}),
|
||
"Waits for signal 'signal-name', message 'message-type', or during 'duration' seconds",
|
||
GST_VALIDATE_ACTION_TYPE_DOESNT_NEED_PIPELINE);
|
||
|
||
REGISTER_ACTION_TYPE ("dot-pipeline", _execute_dot_pipeline, NULL,
|
||
"Dots the pipeline (the 'name' property will be used in the dot filename).\n"
|
||
"For more information have a look at the GST_DEBUG_BIN_TO_DOT_FILE documentation.\n"
|
||
"Note that the GST_DEBUG_DUMP_DOT_DIR env variable needs to be set",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("set-rank", _execute_set_rank_or_disable_feature,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "name",
|
||
.description = "The name of a GstFeature or GstPlugin",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL},
|
||
{
|
||
.name = "rank",
|
||
.description = "The GstRank to set on @name",
|
||
.mandatory = TRUE,
|
||
.types = "string, int",
|
||
NULL},
|
||
{NULL}
|
||
}),
|
||
"Changes the ranking of a particular plugin feature(s)",
|
||
GST_VALIDATE_ACTION_TYPE_CONFIG);
|
||
|
||
REGISTER_ACTION_TYPE ("remove-feature", _execute_set_rank_or_disable_feature,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "name",
|
||
.description = "The name of a GstFeature or GstPlugin to remove",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Remove a plugin feature(s) or a plugin from the registry",
|
||
GST_VALIDATE_ACTION_TYPE_CONFIG);
|
||
|
||
REGISTER_ACTION_TYPE ("set-feature-rank", _execute_set_rank_or_disable_feature,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "feature-name",
|
||
.description = "The name of a GstFeature",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL},
|
||
{
|
||
.name = "rank",
|
||
.description = "The GstRank to set on @feature-name",
|
||
.mandatory = TRUE,
|
||
.types = "string, int",
|
||
NULL},
|
||
{NULL}
|
||
}),
|
||
"Changes the ranking of a particular plugin feature",
|
||
GST_VALIDATE_ACTION_TYPE_CONFIG);
|
||
|
||
REGISTER_ACTION_TYPE ("set-state", _execute_set_state,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "state",
|
||
.description = "A GstState as a string, should be in: \n"
|
||
" * ['null', 'ready', 'paused', 'playing']",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Changes the state of the pipeline to any GstState",
|
||
GST_VALIDATE_ACTION_TYPE_ASYNC & GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK);
|
||
|
||
REGISTER_ACTION_TYPE ("set-vars", _execute_define_vars,
|
||
((GstValidateActionParameter []) {
|
||
{NULL}
|
||
}),
|
||
"Define vars to be used in other actions.\n"
|
||
"For example you can define vars for buffer checksum"
|
||
" to be used in the \"check-last-sample\" action type as follow:\n\n"
|
||
"```\n"
|
||
" set-vars, frame1=SomeRandomHash1,frame2=Anotherhash...\n"
|
||
" check-last-sample, checksum=frame1\n"
|
||
"```\n",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
GST_TYPE_INTERPOLATION_CONTROL_SOURCE;
|
||
GST_TYPE_TRIGGER_CONTROL_SOURCE;
|
||
REGISTER_ACTION_TYPE ("set-timed-value-properties", _set_timed_value_property,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "binding-type",
|
||
.description = "The name of the type of binding to use",
|
||
.types = "string",
|
||
.mandatory = FALSE,
|
||
.def = "direct",
|
||
},
|
||
{
|
||
.name = "source-type",
|
||
.description = "The name of the type of ControlSource to use",
|
||
.types = "string",
|
||
.mandatory = FALSE,
|
||
.def = "GstInterpolationControlSource",
|
||
},
|
||
{
|
||
.name = "interpolation-mode",
|
||
.description = "The name of the GstInterpolationMode to set on the source",
|
||
.types = "string",
|
||
.mandatory = FALSE,
|
||
.def = "linear",
|
||
},
|
||
{
|
||
.name = "timestamp",
|
||
.description = "The timestamp of the keyframe",
|
||
.types = "string or float (GstClockTime)",
|
||
.mandatory = TRUE,
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Sets GstTimedValue on pads on elements properties using GstControlBindings\n"
|
||
"and GstControlSource as defined in the parameters.\n"
|
||
"The properties values to set will be defined as:\n\n"
|
||
"```\n"
|
||
"element-name.padname::property-name=new-value\n"
|
||
"```\n\n"
|
||
"> NOTE: `.padname` is not needed if setting a property on an element\n\n"
|
||
"This action also adds necessary control source/control bindings.\n",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("check-properties", _execute_set_or_check_properties,
|
||
((GstValidateActionParameter []) {
|
||
{NULL}
|
||
}),
|
||
"Check elements and pads properties values.\n"
|
||
"The properties values to check will be defined as:\n\n"
|
||
"```\n"
|
||
"element-name.padname::property-name\n"
|
||
"```\n\n"
|
||
"> NOTE: `.padname` is not needed if checking an element property\n\n",
|
||
GST_VALIDATE_ACTION_TYPE_CHECK);
|
||
|
||
REGISTER_ACTION_TYPE ("set-properties", _execute_set_or_check_properties,
|
||
((GstValidateActionParameter []) {
|
||
{NULL}
|
||
}),
|
||
"Set elements and pads properties values.\n"
|
||
"The properties values to set will be defined as:\n\n"
|
||
"```\n"
|
||
" element-name.padname::property-name\n"
|
||
"```\n\n"
|
||
"> NOTE: `.padname` is not needed if set an element property\n\n",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("set-property", _execute_set_or_check_property,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "target-element-name",
|
||
.description = "The name of the GstElement to set a property on",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "target-element-factory-name",
|
||
.description = "The name factory for which to set a property on built elements",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "target-element-klass",
|
||
.description = "The klass of the GstElements to set a property on",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "property-name",
|
||
.description = "The name of the property to set on @target-element-name",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "property-value",
|
||
.description = "The value of @property-name to be set on the element",
|
||
.mandatory = TRUE,
|
||
.types = "The same type of @property-name",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "on-all-instances",
|
||
.description = "Whether to set property on all instances matching "
|
||
"the requirements",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Sets a property of an element or klass of elements in the pipeline.\n"
|
||
"Besides property-name and value, either 'target-element-name' or\n"
|
||
"'target-element-klass' needs to be defined",
|
||
GST_VALIDATE_ACTION_TYPE_CAN_EXECUTE_ON_ADDITION |
|
||
GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL |
|
||
GST_VALIDATE_ACTION_TYPE_HANDLED_IN_CONFIG);
|
||
type->prepare = gst_validate_set_property_prepare_func;
|
||
|
||
REGISTER_ACTION_TYPE("check-property", _execute_set_or_check_property,
|
||
((GstValidateActionParameter[]) {
|
||
{ .name = "target-element-name",
|
||
.description = "The name of the GstElement to check a property value",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL },
|
||
{ .name = "target-element-factory-name",
|
||
.description = "The name factory for which to check a property value on built elements",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL },
|
||
{ .name = "target-element-klass",
|
||
.description = "The klass of the GstElements to check a property on",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL },
|
||
{ .name = "property-name",
|
||
.description = "The name of the property to set on @target-element-name",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL },
|
||
{ .name = "property-value",
|
||
.description = "The expected value of @property-name",
|
||
.mandatory = TRUE,
|
||
.types = "The same type of @property-name",
|
||
NULL },
|
||
{ NULL } }),
|
||
"Check the value of property of an element or klass of elements in the pipeline.\n"
|
||
"Besides property-name and value, either 'target-element-name' or\n"
|
||
"'target-element-klass' needs to be defined",
|
||
GST_VALIDATE_ACTION_TYPE_CHECK);
|
||
|
||
REGISTER_ACTION_TYPE ("set-debug-threshold",
|
||
_execute_set_debug_threshold,
|
||
((GstValidateActionParameter [])
|
||
{
|
||
{
|
||
.name = "debug-threshold",
|
||
.description = "String defining debug threshold\n"
|
||
"See gst_debug_set_threshold_from_string",
|
||
.mandatory = TRUE,
|
||
.types = "string"},
|
||
{NULL}
|
||
}),
|
||
"Sets the debug level to be used, same format as\n"
|
||
"setting the GST_DEBUG env variable",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("emit-signal", _execute_emit_signal,
|
||
((GstValidateActionParameter [])
|
||
{
|
||
{
|
||
.name = "target-element-name",
|
||
.description = "The name of the GstElement to emit a signal on",
|
||
.mandatory = TRUE,
|
||
.types = "string"
|
||
},
|
||
{
|
||
.name = "signal-name",
|
||
.description = "The name of the signal to emit on @target-element-name",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "params",
|
||
.description = "The signal parameters",
|
||
.mandatory = FALSE,
|
||
.types = "ValueArray",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Emits a signal to an element in the pipeline",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("appsrc-push", _execute_appsrc_push,
|
||
((GstValidateActionParameter [])
|
||
{
|
||
{
|
||
.name = "target-element-name",
|
||
.description = "The name of the appsrc to push data on",
|
||
.mandatory = TRUE,
|
||
.types = "string"
|
||
},
|
||
{
|
||
.name = "fill-mode",
|
||
.description = "How to fill the buffer, possible values:\n"
|
||
" - `nothing`: Leave data as malloc)\n"
|
||
" - `zero`: Fill buffers with zeros\n"
|
||
" - `counter`: Buffers are filled with an ever increasing counter\n"
|
||
" - `file`: Read data from file",
|
||
.mandatory = FALSE,
|
||
.def = "file",
|
||
.types = "string",
|
||
},
|
||
{
|
||
.name = "file-name",
|
||
.description = "Relative path to a file whose contents will be pushed as a buffer",
|
||
.mandatory = FALSE,
|
||
.types = "string"
|
||
},
|
||
{
|
||
.name = "offset",
|
||
.description = "Offset within the file where the buffer will start",
|
||
.mandatory = FALSE,
|
||
.types = "uint64"
|
||
},
|
||
{
|
||
.name = "size",
|
||
.description = "Number of bytes from the file that will be pushed as a buffer",
|
||
.mandatory = FALSE,
|
||
.types = "uint64"
|
||
},
|
||
{
|
||
.name = "caps",
|
||
.description = "Caps for the buffer to be pushed",
|
||
.mandatory = FALSE,
|
||
.types = "caps"
|
||
},
|
||
{
|
||
.name = "pts",
|
||
.description = "Buffer PTS",
|
||
.mandatory = FALSE,
|
||
.types = "GstClockTime"
|
||
},
|
||
{
|
||
.name = "dts",
|
||
.description = "Buffer DTS",
|
||
.mandatory = FALSE,
|
||
.types = "GstClockTime"
|
||
},
|
||
{
|
||
.name = "duration",
|
||
.description = "Buffer duration",
|
||
.mandatory = FALSE,
|
||
.types = "GstClockTime"
|
||
},
|
||
{
|
||
.name = "segment",
|
||
.description = "The GstSegment to configure as part of the sample",
|
||
.mandatory = FALSE,
|
||
.types = "(GstStructure)segment,"
|
||
"[start=(GstClockTime)]"
|
||
"[stop=(GstClockTime)]"
|
||
"[base=(GstClockTime)]"
|
||
"[offset=(GstClockTime)]"
|
||
"[time=(GstClockTime)]"
|
||
"[postion=(GstClockTime)]"
|
||
"[duration=(GstClockTime)]"
|
||
},
|
||
{
|
||
.name = "from-appsink",
|
||
.description = "Pull sample from another appsink, "
|
||
"if appsink is in another pipeline, "
|
||
"use the `other-pipeline-name/target-element-name` synthax",
|
||
.mandatory = FALSE,
|
||
.types = "string"
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Queues a sample in an appsrc. If the pipeline state allows flow of buffers, "
|
||
" the next action is not run until the buffer has been pushed.",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("appsrc-eos", _execute_appsrc_eos,
|
||
((GstValidateActionParameter [])
|
||
{
|
||
{
|
||
.name = "target-element-name",
|
||
.description = "the name of the appsrc to emit eos on",
|
||
.mandatory = TRUE,
|
||
.types = "string"
|
||
},
|
||
{NULL}
|
||
}),
|
||
"queues a eos event in an appsrc.",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("appsink-forward-to-appsrc", _execute_forward_appsink_to_appsrc,
|
||
((GstValidateActionParameter [])
|
||
{
|
||
{
|
||
.name = "sink",
|
||
.description = "the name of the appsink to forward samples/events from",
|
||
.mandatory = TRUE,
|
||
.types = "string"
|
||
},
|
||
{
|
||
.name = "src",
|
||
.description = "the name of the appsrc to forward samples/events to",
|
||
.mandatory = TRUE,
|
||
.types = "string"
|
||
},
|
||
{
|
||
.name = "forward-eos",
|
||
.description = "Wether to forward EOS or not",
|
||
.mandatory = FALSE,
|
||
.def = "true",
|
||
.types = "bool"
|
||
},
|
||
{NULL}
|
||
}),
|
||
"queues a eos event in an appsrc.",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("flush", _execute_flush,
|
||
((GstValidateActionParameter [])
|
||
{
|
||
{
|
||
.name = "target-element-name",
|
||
.description = "The name of the appsrc to flush on",
|
||
.mandatory = TRUE,
|
||
.types = "string"
|
||
},
|
||
{
|
||
.name = "reset-time",
|
||
.description = "Whether the flush should reset running time",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.def = "TRUE"
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Sends FLUSH_START and FLUSH_STOP events.",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("disable-plugin", _execute_disable_plugin,
|
||
((GstValidateActionParameter [])
|
||
{
|
||
{
|
||
.name = "plugin-name",
|
||
.description = "The name of the GstPlugin to disable",
|
||
.mandatory = TRUE,
|
||
.types = "string"
|
||
},
|
||
{
|
||
.name = "as-config",
|
||
.description = "Execute action as a config action (meaning when loading the scenario)",
|
||
.mandatory = FALSE,
|
||
.types = "boolean",
|
||
.def = "false"
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Disables a GstPlugin",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE ("check-last-sample", _execute_check_last_sample,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "sink-name",
|
||
.description = "The name of the sink element to check sample on.",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "sink-factory-name",
|
||
.description = "The name of the factory of the sink element to check sample on.",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "sinkpad-caps",
|
||
.description = "The caps (as string) of the sink to check.",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "checksum",
|
||
.description = "The reference checksum of the buffer.",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "timecode-frame-number",
|
||
.description = "The frame number of the buffer as specified on its"
|
||
" GstVideoTimeCodeMeta",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Checks the last-sample checksum or frame number (set on its "
|
||
" GstVideoTimeCodeMeta) on declared Sink element."
|
||
" This allows checking the checksum of a buffer after a 'seek' or after a"
|
||
" GESTimeline 'commit'"
|
||
" for example",
|
||
GST_VALIDATE_ACTION_TYPE_NON_BLOCKING | GST_VALIDATE_ACTION_TYPE_CHECK);
|
||
|
||
REGISTER_ACTION_TYPE ("crank-clock", _execute_crank_clock,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "expected-time",
|
||
.description = "Expected clock time after cranking",
|
||
.mandatory = FALSE,
|
||
.types = "GstClockTime",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "expected-elapsed-time",
|
||
.description = "Check time elapsed during the clock cranking",
|
||
.mandatory = FALSE,
|
||
.types = "GstClockTime",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}), "Crank the clock, possibly checking how much time was supposed to be waited on the clock"
|
||
" and/or the clock running time after the crank."
|
||
" Using one `crank-clock` action in a scenario implies that the scenario is driving the "
|
||
" clock and a #GstTestClock will be used. The user will need to crank it the number of "
|
||
" time required (using the `repeat` parameter comes handy here).",
|
||
GST_VALIDATE_ACTION_TYPE_NEEDS_CLOCK);
|
||
|
||
REGISTER_ACTION_TYPE ("video-request-key-unit", _execute_request_key_unit,
|
||
((GstValidateActionParameter []) {
|
||
{
|
||
.name = "direction",
|
||
.description = "The direction for the event to travel, should be in\n"
|
||
" * [upstream, downstream]",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "running-time",
|
||
.description = "The running_time can be set to request a new key unit at a specific running_time.\n"
|
||
"If not set, GST_CLOCK_TIME_NONE will be used so upstream elements will produce a new key unit "
|
||
"as soon as possible.",
|
||
.mandatory = FALSE,
|
||
.types = "double or string",
|
||
.possible_variables = "position: The current position in the stream\n"
|
||
"duration: The duration of the stream",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "all-headers",
|
||
.description = "TRUE to produce headers when starting a new key unit",
|
||
.mandatory = FALSE,
|
||
.def = "FALSE",
|
||
.types = "boolean",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "count",
|
||
.description = "integer that can be used to number key units",
|
||
.mandatory = FALSE,
|
||
.def = "0",
|
||
.types = "int",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "target-element-name",
|
||
.description = "The name of the GstElement to send a send force-key-unit to",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "target-element-factory-name",
|
||
.description = "The factory name of the GstElements to send a send force-key-unit to",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "target-element-klass",
|
||
.description = "The klass of the GstElements to send a send force-key-unit to",
|
||
.mandatory = FALSE,
|
||
.def = "Video/Encoder",
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "pad",
|
||
.description = "The name of the GstPad to send a send force-key-unit to",
|
||
.mandatory = FALSE,
|
||
.def = "sink",
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "srcpad",
|
||
.description = "The name of the GstPad to send a send force-key-unit to",
|
||
.mandatory = FALSE,
|
||
.def = "src",
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Request a video key unit", FALSE);
|
||
|
||
REGISTER_ACTION_TYPE("check-position", _execute_check_position,
|
||
((GstValidateActionParameter[]) {
|
||
{ .name = "expected-position",
|
||
.description = "The expected pipeline position",
|
||
.mandatory = TRUE,
|
||
.types = "GstClockTime",
|
||
NULL },
|
||
{NULL}
|
||
}),
|
||
"Check current pipeline position.\n",
|
||
/* FIXME: Make MT safe so it can be marked as GST_VALIDATE_ACTION_TYPE_CHECK */
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE("check-current-pad-caps", _execute_check_pad_caps,
|
||
((GstValidateActionParameter[]) {
|
||
{
|
||
.name = "expected-caps",
|
||
.description = "The expected caps. If not present, expected no caps to be set",
|
||
.mandatory = FALSE,
|
||
.types = "caps,structure",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "target-element-name",
|
||
.description = "The name of the GstElement to send a send force-key-unit to",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "target-element-factory-name",
|
||
.description = "The factory name of the GstElements to get pad from",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "target-element-klass",
|
||
.description = "The klass of the GstElements to get pad from",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "pad",
|
||
.description = "The name of the GstPad to get pad from",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "comparison-type",
|
||
.description = "",
|
||
.mandatory = FALSE,
|
||
.types = "string in [intersect, equal]",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Check currently set caps on a particular pad.\n",
|
||
GST_VALIDATE_ACTION_TYPE_NONE | GST_VALIDATE_ACTION_TYPE_CHECK );
|
||
|
||
REGISTER_ACTION_TYPE("run-command", _run_command,
|
||
((GstValidateActionParameter[]) {
|
||
{
|
||
.name = "argv",
|
||
.description = "The subprocess arguments, include the program name itself",
|
||
.mandatory = TRUE,
|
||
.types = "(string){array,}",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "env",
|
||
.description = "Extra environment variables to set",
|
||
.mandatory = FALSE,
|
||
.types = "structure",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Run an external command.\n",
|
||
GST_VALIDATE_ACTION_TYPE_CAN_BE_OPTIONAL);
|
||
|
||
REGISTER_ACTION_TYPE("foreach", NULL,
|
||
((GstValidateActionParameter[]) {
|
||
{ .name = "actions",
|
||
.description = "The array of actions to repeat",
|
||
.mandatory = TRUE,
|
||
.types = "{array of [structures]}",
|
||
NULL },
|
||
{ NULL } }),
|
||
"Run actions defined in the `actions` array the number of times specified\n"
|
||
"with an iterator parameter passed in. The iterator can be\n"
|
||
"a range like: `i=[start, end, step]` or array of values\n"
|
||
"such as: `values=<value1, value2>`.\n"
|
||
"One and only one iterator field is supported as parameter.",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
type->prepare = gst_validate_foreach_prepare;
|
||
|
||
REGISTER_ACTION_TYPE("run-on-sub-pipeline", _execute_on_sub_scenario,
|
||
((GstValidateActionParameter[]) {
|
||
{
|
||
.name = "pipeline-name",
|
||
.description = "The name of the sub scenario pipeline",
|
||
.mandatory = TRUE,
|
||
.types = "(string)",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "action",
|
||
.description = "The action to execute on @pipeline-name",
|
||
.mandatory = FALSE,
|
||
.types = "[structures]",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Execute @action on a sub scenario/pipeline.\n",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE("create-sub-pipeline", _create_sub_pipeline,
|
||
((GstValidateActionParameter[]) {
|
||
{
|
||
.name = "name",
|
||
.description = "The name of the new pipeline",
|
||
.mandatory = FALSE,
|
||
.types = "(string)",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "desc",
|
||
.description = "Pipeline description as passed to gst_parse_launch()",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "scenario",
|
||
.description = "Array of action and metadatas to run on the new pipeline",
|
||
.mandatory = FALSE,
|
||
.types = "{array of [structures]}",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Start another pipeline potentially running a scenario on it. \n"
|
||
"When a scenario is specified, and while the sub pipeline is running\n"
|
||
" it will be possible to execute actions from the main scenario on that pipeline\n"
|
||
" using the `run-on-sub-pipeline` action type.",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE("http-request", _execute_http_request,
|
||
((GstValidateActionParameter[]) {
|
||
{
|
||
.name = "uri",
|
||
.description = "The URI to send the request to",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "method",
|
||
.description = "The HTTP method to use (GET, POST, etc)",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "body",
|
||
.description = "The request body (for POST/PUT requests)",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "headers",
|
||
.description = "The request headers as Content-Type",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
.def = "application/json",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "expected-response",
|
||
.description = "The exact expected response as a string",
|
||
.mandatory = FALSE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Send an HTTP request to a server.\n"
|
||
"\n"
|
||
"NOTE: This is not expected to be usebale on any server but the\n"
|
||
"one started with the `start-http-server` action.\n"
|
||
"\n"
|
||
"Example:\n"
|
||
"``` jproperties\n"
|
||
"http-request,\n"
|
||
" uri=\"http://127.0.0.1:$(http_server_port)/test\",\n"
|
||
" method=POST,\n"
|
||
" body=\"test data\",\n"
|
||
" headers=\"text/plain\"\n"
|
||
"```\n",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
REGISTER_ACTION_TYPE("start-http-server", _execute_start_http_server,
|
||
((GstValidateActionParameter[]) {
|
||
{
|
||
.name = "working-directory",
|
||
.description = "Server’s current working directory",
|
||
.mandatory = TRUE,
|
||
.types = "string",
|
||
NULL
|
||
},
|
||
{
|
||
.name = "no-pipe",
|
||
.description = "Do not pipe http server stderr/stdout ",
|
||
.mandatory = FALSE,
|
||
.types = "bool",
|
||
.def = "false",
|
||
NULL,
|
||
},
|
||
{NULL}
|
||
}),
|
||
"Start an HTTP server in a separate process to serve files from $(working-directory).\n"
|
||
"The server is started on any available port and the action sets the `$(http_server_port)`\n"
|
||
"variable so it can be used afterward. For subsequent servers started, the\n"
|
||
"variable names become `http_server_port_1`, `http_server_port_2`, etc.\n\n"
|
||
"The server implementation must be specified through the GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH\n"
|
||
"environment variable. By default, it uses our `RangeHTTPServer.py` implementation which\n"
|
||
"provides support for HTTP range requests and directory listing as well as specific POST requests\n"
|
||
"for testing purposes (check the file for details).\n\n"
|
||
"Example:\n"
|
||
"```\n"
|
||
"- start-http-server, working-directory=/path/to/media/files\n"
|
||
"- set-property, playbin::uri=\"http://127.0.0.1:$(http_server_port)/video.mp4\"\n"
|
||
"```\n",
|
||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||
|
||
/* Internal actions types to test the validate scenario implementation */
|
||
REGISTER_ACTION_TYPE("priv_check-action-type-calls",
|
||
_execute_check_action_type_calls, NULL, NULL, 0);
|
||
REGISTER_ACTION_TYPE("priv_check-subaction-level",
|
||
_execute_check_subaction_level, NULL, NULL, 0);
|
||
/* *INDENT-ON* */
|
||
}
|
||
|
||
void
|
||
gst_validate_scenario_deinit (void)
|
||
{
|
||
_free_action_types (action_types);
|
||
action_types = NULL;
|
||
}
|