diff --git a/subprojects/gst-devtools/meson.build b/subprojects/gst-devtools/meson.build index 2bcb013d35..0909e8c5e5 100644 --- a/subprojects/gst-devtools/meson.build +++ b/subprojects/gst-devtools/meson.build @@ -83,6 +83,8 @@ gst_dep = dependency('gstreamer-' + apiversion, version : gst_req, fallback : ['gstreamer', 'gst_dep']) gstbase_dep = dependency('gstreamer-base-' + apiversion, version : gst_req, fallback : ['gstreamer', 'gst_base_dep']) +gstapp_dep = dependency('gstreamer-app-' + apiversion, version : gst_req, + fallback : ['gst-plugins-base', 'app_dep']) gst_pbutils_dep = dependency('gstreamer-pbutils-' + apiversion, version : gst_req, fallback : ['gst-plugins-base', 'pbutils_dep']) gst_video_dep = dependency('gstreamer-video-' + apiversion, version : gst_req, diff --git a/subprojects/gst-devtools/validate/gst/validate/gst-validate-internal.h b/subprojects/gst-devtools/validate/gst/validate/gst-validate-internal.h index 30921fb5c6..5182dbce7b 100644 --- a/subprojects/gst-devtools/validate/gst/validate/gst-validate-internal.h +++ b/subprojects/gst-devtools/validate/gst/validate/gst-validate-internal.h @@ -25,6 +25,7 @@ #include #include "gst-validate-scenario.h" #include "gst-validate-monitor.h" +#include "gst-validate-pipeline-monitor.h" #include extern G_GNUC_INTERNAL GstDebugCategory *gstvalidate_debug; @@ -65,9 +66,14 @@ G_GNUC_INTERNAL gboolean gst_validate_send (JsonNode * root); G_GNUC_INTERNAL void gst_validate_set_test_file_globals (GstStructure* meta, const gchar* testfile, gboolean use_fakesinks); G_GNUC_INTERNAL gboolean gst_validate_get_test_file_scenario (GList** structs, const gchar** scenario_name, gchar** original_name); G_GNUC_INTERNAL GstValidateScenario* gst_validate_scenario_from_structs (GstValidateRunner* runner, GstElement* pipeline, GList* structures, - gchar* origin_file); + const gchar* origin_file); G_GNUC_INTERNAL GList* gst_validate_get_config (const gchar *structname); G_GNUC_INTERNAL GList * gst_validate_get_test_file_expected_issues (void); +G_GNUC_INTERNAL GstValidatePipelineMonitor * +gst_validate_pipeline_monitor_new_full (GstPipeline * pipeline, + GstValidateRunner * runner, GstValidateMonitor * parent, + const gchar *scenario_name, GList *actions, + gboolean find_scenario); G_GNUC_INTERNAL gboolean gst_validate_extra_checks_init (void); G_GNUC_INTERNAL gboolean gst_validate_flow_init (void); diff --git a/subprojects/gst-devtools/validate/gst/validate/gst-validate-pipeline-monitor.c b/subprojects/gst-devtools/validate/gst/validate/gst-validate-pipeline-monitor.c index 69be92dd8f..20707c5b81 100644 --- a/subprojects/gst-devtools/validate/gst/validate/gst-validate-pipeline-monitor.c +++ b/subprojects/gst-devtools/validate/gst/validate/gst-validate-pipeline-monitor.c @@ -759,10 +759,11 @@ _bus_handler (GstBus * bus, GstMessage * message, } static void -gst_validate_pipeline_monitor_create_scenarios (GstValidateBinMonitor * monitor) +gst_validate_pipeline_monitor_create_scenarios (GstValidateBinMonitor * monitor, + const gchar * scenario_name, GList * actions, gboolean find_scenario) { /* scenarios currently only make sense for pipelines */ - const gchar *scenarios_names, *scenario_name = NULL; + const gchar *scenarios_names, *testfile_scenario_name = NULL; gchar **scenarios = NULL, *testfile = NULL; GstObject *target = gst_validate_monitor_get_target (GST_VALIDATE_MONITOR (monitor)); @@ -770,18 +771,24 @@ gst_validate_pipeline_monitor_create_scenarios (GstValidateBinMonitor * monitor) gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (monitor)); GList *scenario_structs = NULL; - if (gst_validate_get_test_file_scenario (&scenario_structs, &scenario_name, - &testfile)) { - if (scenario_name) { + if (!find_scenario + || gst_validate_get_test_file_scenario (&scenario_structs, + &testfile_scenario_name, &testfile)) { + if (testfile_scenario_name || scenario_name) { monitor->scenario = gst_validate_scenario_factory_create (runner, - GST_ELEMENT_CAST (target), scenario_name); + GST_ELEMENT_CAST (target), + scenario_name ? scenario_name : testfile_scenario_name); goto done; } - monitor->scenario = - gst_validate_scenario_from_structs (runner, - GST_ELEMENT_CAST (target), scenario_structs, testfile); + if (actions || scenario_structs) { + monitor->scenario = + gst_validate_scenario_from_structs (runner, + GST_ELEMENT_CAST (target), + scenario_structs ? scenario_structs : actions, + find_scenario ? testfile : "subscenario"); + } goto done; } @@ -820,17 +827,10 @@ done: gst_object_unref (runner); } -/** - * gst_validate_pipeline_monitor_new: - * @pipeline: (transfer none): a #GstPipeline to run Validate on - * @runner: (transfer none): a #GstValidateRunner - * @parent: (nullable): The parent of the new monitor - * - * Returns: (transfer full): A #GstValidatePipelineMonitor - */ GstValidatePipelineMonitor * -gst_validate_pipeline_monitor_new (GstPipeline * pipeline, - GstValidateRunner * runner, GstValidateMonitor * parent) +gst_validate_pipeline_monitor_new_full (GstPipeline * pipeline, + GstValidateRunner * runner, GstValidateMonitor * parent, + const gchar * scenario_name, GList * actions, gboolean find_scenario) { GstBus *bus; GstValidatePipelineMonitor *monitor; @@ -844,7 +844,7 @@ gst_validate_pipeline_monitor_new (GstPipeline * pipeline, "pipeline", pipeline, NULL); gst_validate_pipeline_monitor_create_scenarios (GST_VALIDATE_BIN_MONITOR - (monitor)); + (monitor), scenario_name, actions, find_scenario); bus = gst_element_get_bus (GST_ELEMENT (pipeline)); gst_bus_enable_sync_message_emission (bus); @@ -866,3 +866,20 @@ gst_validate_pipeline_monitor_new (GstPipeline * pipeline, return monitor; } + + +/** + * gst_validate_pipeline_monitor_new: + * @pipeline: (transfer none): a #GstPipeline to run Validate on + * @runner: (transfer none): a #GstValidateRunner + * @parent: (nullable): The parent of the new monitor + * + * Returns: (transfer full): A #GstValidatePipelineMonitor + */ +GstValidatePipelineMonitor * +gst_validate_pipeline_monitor_new (GstPipeline * pipeline, + GstValidateRunner * runner, GstValidateMonitor * parent) +{ + return gst_validate_pipeline_monitor_new_full (pipeline, + runner, parent, NULL, NULL, TRUE); +} diff --git a/subprojects/gst-devtools/validate/gst/validate/gst-validate-report.c b/subprojects/gst-devtools/validate/gst/validate/gst-validate-report.c index f13d92cd77..4443edf569 100644 --- a/subprojects/gst-devtools/validate/gst/validate/gst-validate-report.c +++ b/subprojects/gst-devtools/validate/gst/validate/gst-validate-report.c @@ -1005,11 +1005,14 @@ gst_validate_print_action (GstValidateAction * action, const gchar * message) if (message == NULL) { gint indent = (gst_validate_action_get_level (action) * 2); PrintActionFieldData d = { NULL, indent, 0 }; + GstValidateScenario *scenario = gst_validate_action_get_scenario (action); d.str = string = g_string_new (NULL); - g_string_append_printf (string, "`%s` at %s:%d", action->type, + g_string_append_printf (string, "`%s` at %s:%d(%s)", action->type, GST_VALIDATE_ACTION_FILENAME (action), - GST_VALIDATE_ACTION_LINENO (action)); + GST_VALIDATE_ACTION_LINENO (action), + scenario ? GST_OBJECT_NAME (scenario) : "no scenario"); + gst_object_unref (scenario); if (GST_VALIDATE_ACTION_N_REPEATS (action)) g_string_append_printf (string, " [%s=%d/%d]", diff --git a/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c b/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c index c76c2d63e4..b2cca73b1a 100644 --- a/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c +++ b/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c @@ -379,6 +379,7 @@ struct _GstValidateActionPrivate GMainContext *context; GValue it_value; + GWeakRef sub_pipeline; }; static JsonNode * @@ -497,6 +498,7 @@ _action_free (GstValidateAction * action) 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)); @@ -589,6 +591,40 @@ _action_check_and_set_printed (GstValidateAction * action) 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) { @@ -1128,10 +1164,10 @@ gst_validate_scenario_execute_seek (GstValidateScenario * scenario, 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'", + " 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); + GST_TIME_ARGS (stop), rate, pipeline); break; default: { @@ -2659,6 +2695,160 @@ _foreach_find_iterator (GQuark field_id, GValue * value, } +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; + } + } + 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 @@ -3245,6 +3435,52 @@ _execute_wait_for_message (GstValidateScenario * scenario, 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) { @@ -3261,6 +3497,9 @@ _execute_wait (GstValidateScenario * scenario, GstValidateAction * action) 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); } @@ -3951,6 +4190,7 @@ _execute_appsrc_push (GstValidateScenario * scenario, 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 @@ -3961,6 +4201,31 @@ _execute_appsrc_push (GstValidateScenario * scenario, 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); + + goto push_sample; + } + structure_get_uint64_permissive (action->structure, "offset", &offset); structure_get_uint64_permissive (action->structure, "size", &size); @@ -4056,17 +4321,6 @@ _execute_appsrc_push (GstValidateScenario * scenario, } } - /* 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); - sample = gst_sample_new (buffer, caps, NULL, NULL); gst_clear_caps (&caps); gst_buffer_unref (buffer); @@ -4105,6 +4359,17 @@ _execute_appsrc_push (GstValidateScenario * scenario, 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, @@ -4127,6 +4392,7 @@ done: g_clear_object (&f); g_clear_object (&finfo); g_clear_object (&stream); + g_clear_object (&pipeline); return res; @@ -4697,8 +4963,8 @@ handle_bus_message (MessageData * d) switch (GST_MESSAGE_TYPE (message)) { case GST_MESSAGE_ASYNC_DONE: - if (!gst_validate_scenario_is_flush_seeking (scenario) && - priv->needs_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) @@ -4998,7 +5264,7 @@ _action_type_has_parameter (GstValidateActionType * atype, static gboolean gst_validate_scenario_load_structures (GstValidateScenario * scenario, - GList * structures, gboolean * is_config, gchar * origin_file) + GList * structures, gboolean * is_config, const gchar * origin_file) { gboolean ret = TRUE; GList *tmp; @@ -5614,12 +5880,15 @@ _element_added_cb (GstBin * bin, GstElement * element, static GstValidateScenario * gst_validate_scenario_new (GstValidateRunner * - runner, GstElement * pipeline, gchar * scenario_name, GList * structures) + 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, NULL); + runner, "name", name, NULL); + g_free (name); g_object_ref_sink (scenario); @@ -5719,7 +5988,7 @@ gst_validate_scenario_new (GstValidateRunner * GstValidateScenario * gst_validate_scenario_from_structs (GstValidateRunner * runner, - GstElement * pipeline, GList * structures, gchar * origin_file) + GstElement * pipeline, GList * structures, const gchar * origin_file) { g_return_val_if_fail (structures, NULL); @@ -5738,8 +6007,7 @@ GstValidateScenario * gst_validate_scenario_factory_create (GstValidateRunner * runner, GstElement * pipeline, const gchar * scenario_name) { - return gst_validate_scenario_new (runner, pipeline, (gchar *) scenario_name, - NULL); + return gst_validate_scenario_new (runner, pipeline, scenario_name, NULL); } static gboolean @@ -6730,6 +6998,125 @@ gst_validate_action_set_done (GstValidateAction * action) 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 : NULL, + scenario_structures, FALSE)); + 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; +} + + /** * gst_validate_action_get_scenario: * @action: The action for which to retrieve the scenario @@ -7400,6 +7787,13 @@ register_action_types (void) .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", @@ -7771,6 +8165,14 @@ register_action_types (void) "[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, " @@ -7782,13 +8184,13 @@ register_action_types (void) { { .name = "target-element-name", - .description = "The name of the appsrc to emit EOS on", + .description = "the name of the appsrc to emit eos on", .mandatory = TRUE, .types = "string" }, {NULL} }), - "Queues a EOS event in an appsrc.", + "queues a eos event in an appsrc.", GST_VALIDATE_ACTION_TYPE_NONE); REGISTER_ACTION_TYPE ("flush", _execute_flush, @@ -8082,6 +8484,58 @@ register_action_types (void) 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); + /* 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); diff --git a/subprojects/gst-devtools/validate/tests/launcher_tests/multipipe.validatetest b/subprojects/gst-devtools/validate/tests/launcher_tests/multipipe.validatetest new file mode 100644 index 0000000000..bee6fea646 --- /dev/null +++ b/subprojects/gst-devtools/validate/tests/launcher_tests/multipipe.validatetest @@ -0,0 +1,67 @@ +set-globals, + appsrc_props="handle-segment-change=true \ + automatic-eos=false \ + min-latency=0 \ + max-latency=-1 \ + format=time \ + is-live=true \ + max-buffers=0 \ + max-bytes=0 \ + max-time=5000000000 \ + leaky-type=downstream" + +meta, + args = { + "appsrc name=src $(appsrc_props) ! videoconvert ! $(videosink) name=sink", + }, + handles-states=true + +# Start a new pipeline with an `appsink` +create-sub-pipeline, + name=subpipeline, + desc="videotestsrc ! timeoverlay ! appsink name=appsink sync=true max-buffers=10 drop=true", + scenario={ + [meta, ignore-eos=true], + } + +# Pull sample from the sub pipeline and push it into the main pipeline appsrc +# as soon as the pipeline will start +appsrc-push, target-element-name=src, from-appsink="subpipeline/appsink" +# And preroll the pipeline +play + +# Seek on the sub pipeline +run-on-sub-pipeline, + pipeline-name=subpipeline, + action=[seek, flags=accurate+flush, start=5.0, stop=15.0] + +# Then forward 10 buffers from sub pipeline to main pipeline +foreach, + i=[0, 10], + actions = { + [ + appsrc-push, target-element-name=src, from-appsink="subpipeline/appsink", + ], + } + +# And seek the sub pipeline again +run-on-sub-pipeline, + pipeline-name=subpipeline, + action=[seek, flags=accurate+flush, start=1.0, stop=5.0] + +# And forward 10 more buffers from sub pipeline to main pipeline +foreach, + i=[0, 10], + actions = { + [ + appsrc-push, target-element-name=src, from-appsink="subpipeline/appsink", + ], + } + +# Stop the sub pipeline +run-on-sub-pipeline, + pipeline-name=subpipeline, + action=[stop] + +# And finish everything by marking main pipeline as EOS +appsrc-eos, target-element-name=src