diff --git a/subprojects/gst-editing-services/plugins/nle/meson.build b/subprojects/gst-editing-services/plugins/nle/meson.build index ccbddcbcb7..13cbd45dd8 100644 --- a/subprojects/gst-editing-services/plugins/nle/meson.build +++ b/subprojects/gst-editing-services/plugins/nle/meson.build @@ -7,10 +7,18 @@ nle_sources = ['nleobject.c', 'gstnle.c' ] +deps = [gst_dep, gstbase_dep] +c_args = ges_c_args +if gstvalidate_dep.found() + deps += [gstvalidate_dep] + nle_sources += ['validate.c'] +endif + + nle = library('gstnle', nle_sources, - dependencies : [gst_dep, gstbase_dep], + dependencies : deps, include_directories: [configinc], - c_args : ges_c_args, + c_args : c_args, install : true, install_dir : plugins_install_dir, ) diff --git a/subprojects/gst-editing-services/plugins/nle/nlecomposition.c b/subprojects/gst-editing-services/plugins/nle/nlecomposition.c index 39ddec6b96..cb217e0e60 100644 --- a/subprojects/gst-editing-services/plugins/nle/nlecomposition.c +++ b/subprojects/gst-editing-services/plugins/nle/nlecomposition.c @@ -3729,3 +3729,105 @@ _nle_composition_remove_object (NleComposition * comp, NleObject * object) return TRUE; } + + + +GstElement *nle_composition_get_nle_object_by_name (NleComposition * comp, + const gchar * name); + +GstElement * +nle_find_object_in_bin_recurse (GstBin * object, const gchar * name) +{ + GstElement *res = gst_bin_get_by_name_recurse_up (GST_BIN (object), name); + + if (res) + return res; + + GstIterator *it = + gst_bin_iterate_all_by_element_factory_name (GST_BIN (object), + "nlecomposition"); + GValue item = G_VALUE_INIT; + + while (gst_iterator_next (it, &item) == GST_ITERATOR_OK) { + GstElement *compo = g_value_get_object (&item); + + if (NLE_IS_COMPOSITION (compo)) { + res = + nle_composition_get_nle_object_by_name (NLE_COMPOSITION (compo), + name); + + if (res) { + g_value_reset (&item); + break; + } + } + + g_value_reset (&item); + } + gst_iterator_free (it); + + return res; +} + +GstElement * +nle_composition_get_nle_object_by_name (NleComposition * comp, + const gchar * name) +{ + NleCompositionPrivate *priv = comp->priv; + GList *l; + GstElement *res = NULL; + + GST_INFO_OBJECT (comp, "Looking for child: %s", name); + + GST_OBJECT_LOCK (comp); + GList *objs = NULL; + + /* FIXME Implement a task to retrieve objects if needed */ + objs = + g_list_copy_deep (priv->objects_start, (GCopyFunc) gst_object_ref, NULL); + GST_OBJECT_UNLOCK (comp); + + /* Check in the list of objects, already added */ + for (l = objs; l; l = l->next) { + NleObject *obj = NLE_OBJECT (l->data); + + if (!g_strcmp0 (GST_OBJECT_NAME (obj), name)) { + res = gst_object_ref (GST_ELEMENT (obj)); + break; + } + + res = nle_find_object_in_bin_recurse (GST_BIN (obj), name); + if (res) { + break; + } + } + + g_list_free_full (objs, (GDestroyNotify) gst_object_unref); + if (res) + goto done; + + ACTIONS_LOCK (comp); + /* Check if the object is about to be added */ + for (GList * tmp = comp->priv->actions; tmp != NULL; tmp = tmp->next) { + Action *act = tmp->data; + + if (ACTION_CALLBACK (act) == G_CALLBACK (_add_object_func)) { + ChildIOData *d = ((GClosure *) act)->data; + + if (!g_strcmp0 (GST_OBJECT_NAME (d->object), name)) { + res = gst_object_ref (GST_ELEMENT (d->object)); + break; + } + + res = nle_find_object_in_bin_recurse (GST_BIN (d->object), name); + if (res) { + break; + } + + } + } + ACTIONS_UNLOCK (comp); + +done: + return res; +} diff --git a/subprojects/gst-editing-services/plugins/nle/nlecomposition.h b/subprojects/gst-editing-services/plugins/nle/nlecomposition.h index 9f5da3d8c3..a5daaf962b 100644 --- a/subprojects/gst-editing-services/plugins/nle/nlecomposition.h +++ b/subprojects/gst-editing-services/plugins/nle/nlecomposition.h @@ -61,6 +61,7 @@ struct _NleCompositionClass }; GType nle_composition_get_type (void) G_GNUC_INTERNAL; +GstElement * nle_find_object_in_bin_recurse (GstBin * object, const gchar *name); G_END_DECLS #endif /* __NLE_COMPOSITION_H__ */ diff --git a/subprojects/gst-editing-services/plugins/nle/nleobject.c b/subprojects/gst-editing-services/plugins/nle/nleobject.c index a1085619e1..aa76033b54 100644 --- a/subprojects/gst-editing-services/plugins/nle/nleobject.c +++ b/subprojects/gst-editing-services/plugins/nle/nleobject.c @@ -56,6 +56,10 @@ GST_DEBUG_CATEGORY_STATIC (nleobject_debug); static GObjectClass *parent_class = NULL; +#ifdef HAVE_GST_VALIDATE +extern void nle_validate_init (void); +#endif + /**************************************************** * Helper macros * ****************************************************/ @@ -334,6 +338,10 @@ nle_object_class_init (NleObjectClass * klass) G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN); gst_type_mark_as_plugin_api (NLE_TYPE_OBJECT, 0); + +#ifdef HAVE_GST_VALIDATE + nle_validate_init (); +#endif } static void diff --git a/subprojects/gst-editing-services/plugins/nle/validate.c b/subprojects/gst-editing-services/plugins/nle/validate.c new file mode 100644 index 0000000000..a50b4954fb --- /dev/null +++ b/subprojects/gst-editing-services/plugins/nle/validate.c @@ -0,0 +1,170 @@ +/* Non Linear Engine plugin + * + * Copyright (C) 2023 Thibault Saunier + * + * validate.c + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "nle.h" + +GST_DEBUG_CATEGORY_STATIC (nle_validate_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT nle_validate_debug + +void nle_validate_init (void); + +#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 +#else /* G_HAVE_GNUC_VARARGS */ +#ifdef 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 /* G_HAVE_ISO_VARARGS */ +#endif /* G_HAVE_GNUC_VARARGS */ + +#define NLE_START_VALIDATE_ACTION(funcname) \ +static gint \ +funcname(GstValidateScenario *scenario, GstValidateAction *action) { \ + GstValidateActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; \ + GstElement * pipeline = gst_validate_scenario_get_pipeline(scenario); + +#define NLE_END_VALIDATE_ACTION \ +done: \ + gst_clear_object(&pipeline); \ + return res; \ +} + +NLE_START_VALIDATE_ACTION (_add_object) +{ + GError *err = NULL; + GstElement *nleobj = NULL; + GstElement *child = + gst_parse_bin_from_description_full (gst_structure_get_string + (action->structure, "desc"), FALSE, NULL, + GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS | GST_PARSE_FLAG_PLACE_IN_BIN, + &err); + const gchar *objname = gst_structure_get_string (action->structure, + "object-name"); + + REPORT_UNLESS (child, clean, "Failed to create element from description: %s", + err ? err->message : "Unknown error"); + + nleobj = nle_find_object_in_bin_recurse (GST_BIN (pipeline), objname); + + REPORT_UNLESS (nleobj, clean, "Could not find object `%s`", objname); + + gboolean is_operation = NLE_IS_OPERATION (nleobj); + gboolean is_src = NLE_IS_SOURCE (nleobj); + if (GST_IS_BIN (child) && (is_src || is_operation)) { + if (child->numsrcpads == 0 && !gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (child), "src")) { + GstPad *srcpad = gst_bin_find_unlinked_pad (GST_BIN (child), GST_PAD_SRC); + if (srcpad) { + gst_element_add_pad (child, gst_ghost_pad_new ("src", srcpad)); + gst_object_unref (srcpad); + } + } + + if (is_operation && child->numsinkpads == 0 + && !gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (child), + "sink")) { + GstPad *sinkpad = + gst_bin_find_unlinked_pad (GST_BIN (child), GST_PAD_SINK); + if (sinkpad) { + gst_element_add_pad (child, gst_ghost_pad_new ("sink", sinkpad)); + gst_object_unref (sinkpad); + } + } + + } + REPORT_UNLESS (gst_bin_add (GST_BIN (nleobj), gst_object_ref (child)), clean, + "Could not add child to nle object"); + +clean: + g_clear_error (&err); + gst_clear_object (&child); + + gst_clear_object (&nleobj); + + goto done; +} + +NLE_END_VALIDATE_ACTION; + +static void +register_action_types () +{ + GST_DEBUG_CATEGORY_INIT (nle_validate_debug, "nlevalidate", + GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "NLE validate"); + +/* *INDENT-OFF* */ + gst_validate_register_action_type ("nle-add-child", "nle", + _add_object, + (GstValidateActionParameter []) { + { + .name = "object-name", + .description = "The name of the nle object to which to add child, will recurse, \n" + " potentially in `nlecompositions` to find the right object", + .mandatory = TRUE, + .types = "string", + }, + { + .name = "desc", + .description = "The 'bin description' of the child to add", + .mandatory = TRUE, + .types = "string", + }, + {NULL} + }, + "Add a child to a NleObject\n", + GST_VALIDATE_ACTION_TYPE_NONE); +/* *INDENT-ON* */ +} + + +void +nle_validate_init () +{ + register_action_types (); +} diff --git a/subprojects/gst-editing-services/tests/meson.build b/subprojects/gst-editing-services/tests/meson.build index 7dea3eb0ee..99e609c0cd 100644 --- a/subprojects/gst-editing-services/tests/meson.build +++ b/subprojects/gst-editing-services/tests/meson.build @@ -5,7 +5,7 @@ endif # FIXME: make check work on windows if host_system != 'windows' and gstcheck_dep.found() subdir('check') + subdir('validate') endif -subdir('validate') -subdir('benchmarks') \ No newline at end of file +subdir('benchmarks') diff --git a/subprojects/gst-editing-services/tests/validate/meson.build b/subprojects/gst-editing-services/tests/validate/meson.build index dc61b42962..ec4c883176 100644 --- a/subprojects/gst-editing-services/tests/validate/meson.build +++ b/subprojects/gst-editing-services/tests/validate/meson.build @@ -7,3 +7,36 @@ install_data (['geslaunch.py'], env = environment() env.prepend('GST_VALIDATE_APPS_DIR', meson.current_source_dir()) meson.add_devenv(env) + +gst_tester = find_program('gst-tester-@0@'.format(apiversion), required: get_option('tests')) +if not gst_tester.found() + subdir_done() +endif + +tests = [ + 'nle/simple_source_playback', + 'nle/simple_source_in_composition_playback', +] + +env = environment() +env.set('GST_PLUGIN_PATH_1_0', meson.global_build_root(), pluginsdirs) +env.set('GST_PLUGIN_SYSTEM_PATH_1_0', '') +env.set('GST_REGISTRY', '@0@/@1@.registry'.format(meson.current_build_dir(), 'validate')) +env.set('GST_PLUGIN_SCANNER_1_0', gst_plugin_scanner_path) +env.set('GST_PLUGIN_LOADING_WHITELIST', 'gstreamer', 'gst-validate', + 'gst-plugins-base@' + meson.project_build_root(), + 'gst-editing-services@' + meson.project_build_root(), +) + +foreach t: tests + test_dir_name = t.split('/') + test_name = 'validate' + foreach c: test_dir_name + test_name += '.' + c + endforeach + test_env = env + test_env.set('GST_VALIDATE_LOGSDIR', join_paths(meson.current_build_dir(), test_name)) + test_file = join_paths(meson.current_source_dir(), t + '.validatetest') + test(test_name, gst_tester, args: [test_file, '--use-fakesinks'], + env: test_env, timeout : 3 * 60, protocol: 'tap') +endforeach diff --git a/subprojects/gst-editing-services/tests/validate/nle/simple_source_in_composition_playback.validatetest b/subprojects/gst-editing-services/tests/validate/nle/simple_source_in_composition_playback.validatetest new file mode 100644 index 0000000000..e522a13556 --- /dev/null +++ b/subprojects/gst-editing-services/tests/validate/nle/simple_source_in_composition_playback.validatetest @@ -0,0 +1,15 @@ +meta, + handles-states=true, + ignore-eos=true, + args={ + "nlecomposition name=compo ! $(videosink)", + } + +nle-add-child, object-name="compo", desc="nlesource name=s inpoint=0 duration=200000000" +nle-add-child, object-name="s", desc="videotestsrc pattern=blue" + +play +check-position, on-message=eos, expected-position=0.2 + +stop + diff --git a/subprojects/gst-editing-services/tests/validate/nle/simple_source_playback.validatetest b/subprojects/gst-editing-services/tests/validate/nle/simple_source_playback.validatetest new file mode 100644 index 0000000000..0ad208d9b4 --- /dev/null +++ b/subprojects/gst-editing-services/tests/validate/nle/simple_source_playback.validatetest @@ -0,0 +1,15 @@ +meta, + handles-states=true, + ignore-eos=true, + args={ + "nlesource name=s inpoint=0 duration=200000000 ! $(videosink)", + } + +nle-add-child, + object-name="s", + desc="videotestsrc ! timeoverlay" + +play +check-position, on-message=eos, expected-position=0.2 + +stop