diff --git a/validate/configure.ac b/validate/configure.ac index 3a7048a8eb..b0a4cd4675 100644 --- a/validate/configure.ac +++ b/validate/configure.ac @@ -38,6 +38,8 @@ dnl GST_API_VERSION=$PACKAGE_VERSION_MAJOR.$PACKAGE_VERSION_MINOR dnl we override it here if we need to for the release candidate of new series GST_API_VERSION=0.10 AC_SUBST(GST_API_VERSION) +AC_DEFINE_UNQUOTED(GST_API_VERSION, "$GST_API_VERSION", + [GStreamer API Version]) AS_LIBTOOL(GST, 0, 0, 0) @@ -138,6 +140,18 @@ fi AC_SUBST(GST_PBUTILS_LIBS) AC_SUBST(GST_PBUTILS_CFLAGS) +dnl needed for scenarios definition files +GST_PREFIX="`$PKG_CONFIG --variable=prefix gstreamer-$GST_API_VERSION`" +AC_SUBST(GST_PREFIX) +GST_DATADIR="$GST_PREFIX/share" +AC_DEFINE_UNQUOTED(GST_DATADIR, "$GST_DATADIR", [system wide data directory]) + +PKG_CHECK_MODULES(GIO, gio-2.0 >= 2.16, HAVE_GIO=yes, HAVE_GIO=no) +AC_SUBST(GIO_CFLAGS) +AC_SUBST(GIO_LIBS) + +dnl checks for gstreamer + AG_GST_CHECK_GST_CHECK($GST_API_VERSION, [$GST_REQ], no) dnl *** set variables based on configure arguments *** @@ -230,6 +244,7 @@ common/Makefile common/m4/Makefile gst/Makefile gst/qa/Makefile +data/Makefile ]) AC_OUTPUT diff --git a/validate/data/Makefile.am b/validate/data/Makefile.am new file mode 100644 index 0000000000..3a2fc92180 --- /dev/null +++ b/validate/data/Makefile.am @@ -0,0 +1,3 @@ +confdir=${sysconfdir}/gstreamer +conf_DATA = simple_seeks.xml +EXTRA_DIST = simple_seeks.xml diff --git a/validate/data/simple_seeks.xml b/validate/data/simple_seeks.xml new file mode 100644 index 0000000000..f4a7d6e70c --- /dev/null +++ b/validate/data/simple_seeks.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/validate/gst/qa/Makefile.am b/validate/gst/qa/Makefile.am index a9c4a9bc26..979effa48a 100644 --- a/validate/gst/qa/Makefile.am +++ b/validate/gst/qa/Makefile.am @@ -9,9 +9,10 @@ c_sources = \ gst-qa-pad-monitor.c \ gst-qa-monitor-factory.c \ gst-qa-report.c \ + gst-qa-scenario.c \ gst-qa-monitor-preload.c -noinst_HEADERS = +noinst_HEADERS = lib_LTLIBRARIES = \ libgstqa-@GST_API_VERSION@.la @@ -19,11 +20,12 @@ lib_LTLIBRARIES = \ libgstqa_@GST_API_VERSION@_la_SOURCES = \ $(c_sources) -libgstqa_@GST_API_VERSION@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) -libgstqa_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) +libgstqa_@GST_API_VERSION@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(GIO_CFLAGS) +libgstqa_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) \ + $(GST_LT_LDFLAGS) $(GIO_LDFLAGS) libgstqa_@GST_API_VERSION@_la_LIBADD = \ $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) \ - $(GST_LIBS) + $(GST_LIBS) $(GIO_LIBS) libgstqa_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/gst/qa libgstqa_@GST_API_VERSION@include_HEADERS = $(public_headers) diff --git a/validate/gst/qa/gst-qa-runner.c b/validate/gst/qa/gst-qa-runner.c index 4bbba0d67a..a5ac231bb8 100644 --- a/validate/gst/qa/gst-qa-runner.c +++ b/validate/gst/qa/gst-qa-runner.c @@ -22,6 +22,7 @@ #include "gst-qa-runner.h" #include "gst-qa-report.h" #include "gst-qa-monitor-factory.h" +#include "gst-qa-scenario.h" /** * SECTION:gst-qa-runner @@ -60,6 +61,9 @@ gst_qa_runner_dispose (GObject * object) if (runner->monitor) g_object_unref (runner->monitor); + if (runner->scenario) + g_object_unref (runner->scenario); + G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -94,11 +98,28 @@ gst_qa_runner_init (GstQaRunner * runner) GstQaRunner * gst_qa_runner_new (GstElement * pipeline) { - GstQaRunner *runner = g_object_new (GST_TYPE_QA_RUNNER, NULL); + const gchar *scenario_name; + GstQaRunner *runner; g_return_val_if_fail (pipeline != NULL, NULL); + runner = g_object_get_data ((GObject *) pipeline, "qa-runner"); + if (runner) { + GST_WARNING_OBJECT (pipeline, + "Pipeline already has a qa-runner associated, returning it"); + + return gst_object_ref (runner); + } + + runner = g_object_new (GST_TYPE_QA_RUNNER, NULL); runner->pipeline = gst_object_ref (pipeline); + + + if ((scenario_name = g_getenv ("GST_QA_SCENARIO"))) + runner->scenario = gst_qa_scenario_factory_create (pipeline, scenario_name); + + g_object_set_data ((GObject *) pipeline, "qa-runner", runner); + return runner; } diff --git a/validate/gst/qa/gst-qa-runner.h b/validate/gst/qa/gst-qa-runner.h index a15a6a71b7..4ab0e9f4f6 100644 --- a/validate/gst/qa/gst-qa-runner.h +++ b/validate/gst/qa/gst-qa-runner.h @@ -31,6 +31,7 @@ G_BEGIN_DECLS /* forward declaration */ typedef struct _GstQaElementMonitor GstQaElementMonitor; +typedef struct _GstQaScenario GstQaScenario; #define GST_TYPE_QA_RUNNER (gst_qa_runner_get_type ()) #define GST_IS_QA_RUNNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_QA_RUNNER)) @@ -60,6 +61,7 @@ struct _GstQaRunner { /*< private >*/ GstElement *pipeline; GstQaElementMonitor *monitor; + GstQaScenario *scenario; GSList *reports; }; diff --git a/validate/gst/qa/gst-qa-scenario.c b/validate/gst/qa/gst-qa-scenario.c new file mode 100644 index 0000000000..6d6f4ca089 --- /dev/null +++ b/validate/gst/qa/gst-qa-scenario.c @@ -0,0 +1,381 @@ +/* GStreamer + * Copyright (C) 2013 Thibault Saunier + * + * gst-qa-scenario.c - QA 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gst-qa-scenario.h" +#include +#include + +#define GST_QA_SCENARIO_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), GST_TYPE_QA_SCENARIO, GstQaScenarioPrivate)) + +#define GST_QA_SCENARIO_SUFFIX ".xml" +#define GST_QA_SCERNARIO_DIRECTORY "qa-scenario" + +GST_DEBUG_CATEGORY_STATIC (gst_qa_scenario); +#define GST_CAT_DEFAULT gst_qa_scenario + + +#define DEFAULT_SEEK_TOLERANCE (0.05 * GST_SECOND) /* tolerance seek interval + TODO make it overridable */ + +static void gst_qa_scenario_class_init (GstQaScenarioClass * klass); +static void gst_qa_scenario_init (GstQaScenario * scenario); +static void gst_qa_scenario_dispose (GObject * object); +static void gst_qa_scenario_finalize (GObject * object); + +G_DEFINE_TYPE (GstQaScenario, gst_qa_scenario, G_TYPE_OBJECT); + +typedef struct _SeekInfo +{ + gchar *name; + GstClockTime seeking_time; + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type; + GstClockTime start; + GstSeekType stop_type; + GstClockTime stop; + +} SeekInfo; + +struct _GstQaScenarioPrivate +{ + GList *seeks; + + GstElement *pipeline; +}; + +/* Some helper method that are missing iin Json itscenario */ +static guint +get_flags_from_string (GType type, const gchar * str_flags) +{ + guint i; + gint flags = 0; + GFlagsClass *class = g_type_class_ref (type); + + for (i = 0; i < class->n_values; i++) { + if (g_strrstr (str_flags, class->values[i].value_nick)) { + flags |= class->values[i].value; + } + } + g_type_class_unref (class); + + return flags; +} + +static void +get_enum_from_string (GType type, const gchar * str_enum, guint * enum_value) +{ + guint i; + GEnumClass *class = g_type_class_ref (type); + + for (i = 0; i < class->n_values; i++) { + if (g_strrstr (str_enum, class->values[i].value_nick)) { + *enum_value = class->values[i].value; + break; + } + } + + g_type_class_unref (class); +} + +static SeekInfo * +_new_seek_info (void) +{ + SeekInfo *info = g_slice_new (SeekInfo); + + info->rate = 1.0; + info->format = GST_FORMAT_TIME; + info->start_type = GST_SEEK_TYPE_SET; + info->stop_type = GST_SEEK_TYPE_SET; + info->flags = GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH; + info->seeking_time = GST_SECOND; + info->start = 0; + info->stop = GST_CLOCK_TIME_NONE; + + return info; +} + +static void +_free_seek_info (SeekInfo * info) +{ + g_slice_free (SeekInfo, info); +} + +static inline void +_parse_seek (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + GstQaScenario * scenario, GError ** error) +{ + GstQaScenarioPrivate *priv = scenario->priv; + const char *seeking_time, *format, *rate, *flags, *start_type, *start, + *stop_type, *stop; + + SeekInfo *info = _new_seek_info (); + + if (!g_markup_collect_attributes (element_name, attribute_names, + attribute_values, error, + G_MARKUP_COLLECT_STRDUP, "name", &info->name, + G_MARKUP_COLLECT_STRING, "seeking_time", &seeking_time, + G_MARKUP_COLLECT_STRING, "format", &format, + G_MARKUP_COLLECT_STRING, "rate", &rate, + G_MARKUP_COLLECT_STRING, "flags", &flags, + G_MARKUP_COLLECT_STRING, "start_type", &start_type, + G_MARKUP_COLLECT_STRING, "start", &start, + G_MARKUP_COLLECT_STRING, "stop_type", &stop_type, + G_MARKUP_COLLECT_STRING, "stop", &stop, G_MARKUP_COLLECT_INVALID)) + return; + + get_enum_from_string (GST_TYPE_FORMAT, format, &info->format); + + info->rate = g_ascii_strtoull (rate, NULL, 10); + info->flags = get_flags_from_string (GST_TYPE_SEEK_FLAGS, flags); + info->seeking_time = g_ascii_strtoull (seeking_time, NULL, 10); + get_enum_from_string (GST_TYPE_SEEK_TYPE, start_type, &info->start_type); + info->start = g_ascii_strtoull (start, NULL, 10); + get_enum_from_string (GST_TYPE_SEEK_TYPE, stop_type, &info->stop_type); + info->stop = g_ascii_strtoull (stop, NULL, 10); + + priv->seeks = g_list_append (priv->seeks, info); +} + +static void +_parse_element_start (GMarkupParseContext * context, const gchar * element_name, + const gchar ** attribute_names, const gchar ** attribute_values, + gpointer scenario, GError ** error) +{ + if (g_strcmp0 (element_name, "seek") == 0) { + _parse_seek (context, element_name, attribute_names, + attribute_values, scenario, error); + } +} + +static gboolean +get_position (GstQaScenario * scenario) +{ + GList *tmp; + gint64 position; + GstFormat format = GST_FORMAT_TIME; + GstQaScenarioPrivate *priv = scenario->priv; + GstElement *pipeline = scenario->priv->pipeline; + + gst_element_query_position (pipeline, &format, &position); + + tmp = scenario->priv->seeks; + GST_DEBUG ("Current position: %" GST_TIME_FORMAT, GST_TIME_ARGS (position)); + while (tmp) { + SeekInfo *seek = tmp->data; + if ((position >= (seek->seeking_time - DEFAULT_SEEK_TOLERANCE)) + && (position <= (seek->seeking_time + DEFAULT_SEEK_TOLERANCE))) { + + GST_LOG ("seeking to: %" GST_TIME_FORMAT " stop: %" GST_TIME_FORMAT, + GST_TIME_ARGS (seek->start), GST_TIME_ARGS (seek->stop)); + + if (gst_element_seek (pipeline, seek->rate, + seek->format, seek->flags, + seek->start_type, seek->start, + seek->stop_type, seek->stop) == FALSE) + GST_ERROR ("FIXME, make it possible to report from the scenario"); + + priv->seeks = g_list_remove_link (priv->seeks, tmp); + g_slice_free (SeekInfo, seek); + g_list_free (tmp); + break; + } + tmp = tmp->next; + } + return TRUE; +} + +static gboolean +_load_scenario_file (GstQaScenario * scenario, const gchar * scenario_file) +{ + gsize xmlsize; + GFile *file = NULL; + GError *err = NULL; + gboolean ret = TRUE; + gchar *xmlcontent = NULL; + GMarkupParseContext *parsecontext = NULL; + GstQaScenarioClass *self_class = GST_QA_SCENARIO_GET_CLASS (scenario); + gchar *uri = gst_filename_to_uri (scenario_file, &err); + + if (uri == NULL) + goto failed; + + GST_DEBUG ("Trying to load %s", scenario_file); + if ((file = g_file_new_for_path (scenario_file)) == NULL) + goto wrong_uri; + + /* TODO Handle GCancellable */ + if (!g_file_load_contents (file, NULL, &xmlcontent, &xmlsize, NULL, &err)) + goto failed; + + if (g_strcmp0 (xmlcontent, "") == 0) + goto failed; + + parsecontext = g_markup_parse_context_new (&self_class->content_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, scenario, NULL); + + if (g_markup_parse_context_parse (parsecontext, xmlcontent, xmlsize, + &err) == FALSE) + goto failed; + +done: + if (xmlcontent) + g_free (xmlcontent); + + if (file) + gst_object_unref (file); + + if (parsecontext) { + g_markup_parse_context_free (parsecontext); + parsecontext = NULL; + } + + return ret; + +wrong_uri: + GST_WARNING ("%s wrong uri", scenario_file); + + ret = FALSE; + goto done; + +failed: + ret = FALSE; + goto done; +} + +gboolean +gst_qa_scenario_load (GstQaScenario * scenario, const gchar * scenario_name) +{ + gboolean ret = TRUE; + gchar *lfilename = NULL, *tldir = NULL; + + if (!scenario_name) + goto invalid_name; + + lfilename = g_strdup_printf ("%s" GST_QA_SCENARIO_SUFFIX, scenario_name); + + /* Try from local profiles */ + tldir = + g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION, + GST_QA_SCERNARIO_DIRECTORY, lfilename, NULL); + + if (!(ret = _load_scenario_file (scenario, tldir))) { + g_free (tldir); + /* Try from system-wide profiles */ + tldir = g_build_filename (GST_DATADIR, "gstreamer-" GST_API_VERSION, + GST_QA_SCERNARIO_DIRECTORY, lfilename, NULL); + ret = _load_scenario_file (scenario, tldir); + } + + /* Hack to make it work uninstalled */ + if (ret == FALSE) { + g_free (tldir); + + tldir = g_build_filename ("data/", lfilename, NULL); + ret = _load_scenario_file (scenario, tldir); + } + +done: + if (tldir) + g_free (tldir); + if (lfilename) + g_free (lfilename); + + return ret; + +invalid_name: + { + GST_ERROR ("Invalid name for encoding target : '%s'", scenario_name); + ret = FALSE; + goto done; + } +} + +static void +gst_qa_scenario_class_init (GstQaScenarioClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (gst_qa_scenario, "gstqascenario", + GST_DEBUG_FG_MAGENTA, "gst qa scenario"); + + g_type_class_add_private (klass, sizeof (GstQaScenarioPrivate)); + + object_class->dispose = gst_qa_scenario_dispose; + object_class->finalize = gst_qa_scenario_finalize; + + klass->content_parser.start_element = _parse_element_start; +} + +static void +gst_qa_scenario_init (GstQaScenario * scenario) +{ + scenario->priv = GST_QA_SCENARIO_GET_PRIVATE (scenario); +} + +static void +gst_qa_scenario_dispose (GObject * object) +{ + GstQaScenarioPrivate *priv = GST_QA_SCENARIO (object)->priv; + + gst_object_unref (priv->pipeline); + g_list_free_full (priv->seeks, (GDestroyNotify) _free_seek_info); + + G_OBJECT_CLASS (gst_qa_scenario_parent_class)->dispose (object); +} + +static void +gst_qa_scenario_finalize (GObject * object) +{ + G_OBJECT_CLASS (gst_qa_scenario_parent_class)->finalize (object); +} + +GstQaScenario * +gst_qa_scenario_factory_create (GstElement * pipeline, + const gchar * scenario_name) +{ + GstQaScenario *scenario = g_object_new (GST_TYPE_QA_SCENARIO, NULL); + + GST_LOG ("Creating scenario %s", scenario_name); + if (!gst_qa_scenario_load (scenario, scenario_name)) { + g_object_unref (scenario); + + return NULL; + } + + scenario->priv->pipeline = gst_object_ref (pipeline); + g_timeout_add (50, (GSourceFunc) get_position, scenario); + + g_print ("\n=========================================\n" + "Running scenario %s on pipeline %s" + "\n=========================================\n", scenario_name, + GST_OBJECT_NAME (pipeline)); + + return scenario; +} diff --git a/validate/gst/qa/gst-qa-scenario.h b/validate/gst/qa/gst-qa-scenario.h new file mode 100644 index 0000000000..66d1b13a6c --- /dev/null +++ b/validate/gst/qa/gst-qa-scenario.h @@ -0,0 +1,63 @@ +/* GStreamer + * Copyright (C) 2013 Thibault Saunier + * + * gst-qa-runner.c - QA Runner 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. + */ + +#ifndef __GST_QA_SCENARIO_H__ +#define __GST_QA_SCENARIO_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_QA_SCENARIO (gst_qa_scenario_get_type ()) +#define GST_QA_SCENARIO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_QA_SCENARIO, GstQaScenario)) +#define GST_QA_SCENARIO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_QA_SCENARIO, GstQaScenarioClass)) +#define GST_IS_QA_SCENARIO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_QA_SCENARIO)) +#define GST_IS_QA_SCENARIO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_QA_SCENARIO)) +#define GST_QA_SCENARIO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_QA_SCENARIO, GstQaScenarioClass)) + +typedef struct _GstQaScenario GstQaScenario; +typedef struct _GstQaScenarioClass GstQaScenarioClass; +typedef struct _GstQaScenarioPrivate GstQaScenarioPrivate; + + +struct _GstQaScenarioClass +{ + GObjectClass parent_class; + + GMarkupParser content_parser; +}; + +struct _GstQaScenario +{ + GObject parent; + + GstQaScenarioPrivate *priv; +}; + +GType gst_qa_scenario_get_type (void); + +GstQaScenario * gst_qa_scenario_factory_create (GstElement *pipeline, + const gchar *scenario_name); + +G_END_DECLS + +#endif /* __GST_QA_SCENARIOS__ */