diff --git a/validate/gst/qa/Makefile.am b/validate/gst/qa/Makefile.am index 94ba88f211..3639d96c69 100644 --- a/validate/gst/qa/Makefile.am +++ b/validate/gst/qa/Makefile.am @@ -13,6 +13,7 @@ c_sources = \ gst-qa-scenario.c \ gst-qa-override.c \ gst-qa-override-registry.c \ + gst-qa-file-checker.c \ gst-qa-monitor-preload.c noinst_HEADERS = diff --git a/validate/gst/qa/gst-qa-file-checker.c b/validate/gst/qa/gst-qa-file-checker.c new file mode 100644 index 0000000000..d885184b3f --- /dev/null +++ b/validate/gst/qa/gst-qa-file-checker.c @@ -0,0 +1,358 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-qa-file-checker.c - QA File conformance check utility functions / structs + * + * 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 "gst-qa-file-checker.h" +#include "gst-qa-reporter.h" + +#include +#include + +enum +{ + PROP_0, + PROP_RUNNER, + PROP_URI, + PROP_PROFILE, + PROP_DURATION, + PROP_DURATION_TOLERANCE, + PROP_FILE_SIZE, + PROP_FILE_SIZE_TOLERANCE, + PROP_SEEKABLE, + PROP_LAST +}; + +#define DEFAULT_DURATION GST_CLOCK_TIME_NONE +#define DEFAULT_DURATION_TOLERANCE 0 +#define DEFAULT_FILE_SIZE 0 +#define DEFAULT_FILE_SIZE_TOLERANCE 0 +#define DEFAULT_SEEKABLE FALSE + +GST_DEBUG_CATEGORY_STATIC (gst_qa_file_checker_debug); +#define GST_CAT_DEFAULT gst_qa_file_checker_debug + +static void +gst_qa_file_checker_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void +gst_qa_file_checker_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + +#define _do_init \ + GST_DEBUG_CATEGORY_INIT (gst_qa_file_checker_debug, "qa_file_checker", 0, "QA FileChecker");\ + G_IMPLEMENT_INTERFACE (GST_TYPE_QA_REPORTER, _reporter_iface_init) + +static void +_reporter_iface_init (GstQaReporterInterface * iface) +{ +} + +#define gst_qa_file_checker_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstQaFileChecker, gst_qa_file_checker, + G_TYPE_OBJECT, _do_init); + +static void +gst_qa_file_checker_dispose (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_qa_file_checker_finalize (GObject * object) +{ + GstQaFileChecker *fc = GST_QA_FILE_CHECKER_CAST (object); + + gst_qa_reporter_set_name (GST_QA_REPORTER (object), NULL); + + g_free (fc->uri); + if (fc->profile) + gst_encoding_profile_unref (fc->profile); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_qa_file_checker_class_init (GstQaFileCheckerClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = gst_qa_file_checker_get_property; + gobject_class->set_property = gst_qa_file_checker_set_property; + gobject_class->dispose = gst_qa_file_checker_dispose; + gobject_class->finalize = gst_qa_file_checker_finalize; + + g_object_class_install_property (gobject_class, PROP_RUNNER, + g_param_spec_object ("qa-runner", "QA Runner", "The QA runner to " + "report errors to", GST_TYPE_QA_RUNNER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_URI, + g_param_spec_string ("uri", "URI", "The URI of the file to be checked", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + g_object_class_install_property (gobject_class, PROP_PROFILE, + gst_param_spec_mini_object ("profile", "Profile", + "The GstEncodingProfile " "that should match what the file contains", + GST_TYPE_ENCODING_PROFILE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + g_object_class_install_property (gobject_class, PROP_DURATION, + g_param_spec_uint64 ("duration", "duration", "Stream duration " + "in nanosecs, use GST_CLOCK_TIME_NONE to disable this check", + 0, G_MAXUINT64, DEFAULT_DURATION, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + g_object_class_install_property (gobject_class, PROP_DURATION_TOLERANCE, + g_param_spec_uint64 ("duration-tolerance", "duration tolerance", + "Acceptable margin of error of the duration check (in nanoseconds)", + 0, G_MAXUINT64, DEFAULT_DURATION_TOLERANCE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + g_object_class_install_property (gobject_class, PROP_FILE_SIZE, + g_param_spec_uint64 ("file-size", "file size", "File size in bytes", + 0, G_MAXUINT64, DEFAULT_FILE_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + g_object_class_install_property (gobject_class, PROP_FILE_SIZE_TOLERANCE, + g_param_spec_uint64 ("file-size-tolerance", "file size tolerance", + "Acceptable margin of error of the file size check (in bytes)", + 0, G_MAXUINT64, DEFAULT_FILE_SIZE_TOLERANCE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + g_object_class_install_property (gobject_class, PROP_SEEKABLE, + g_param_spec_boolean ("is-seekable", "is seekable", + "If the resulting file should be seekable", DEFAULT_SEEKABLE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); +} + +static void +gst_qa_file_checker_init (GstQaFileChecker * fc) +{ + fc->uri = NULL; + fc->profile = NULL; + fc->duration = DEFAULT_DURATION; + fc->duration_tolerance = DEFAULT_DURATION_TOLERANCE; + fc->file_size = DEFAULT_FILE_SIZE; + fc->file_size_tolerance = DEFAULT_FILE_SIZE_TOLERANCE; + fc->seekable = DEFAULT_SEEKABLE; +} + +static void +gst_qa_file_checker_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstQaFileChecker *fc; + + fc = GST_QA_FILE_CHECKER_CAST (object); + + switch (prop_id) { + case PROP_RUNNER: + gst_qa_reporter_set_runner (GST_QA_REPORTER (fc), + g_value_get_object (value)); + break; + case PROP_URI: + g_free (fc->uri); + fc->uri = g_value_dup_string (value); + break; + case PROP_PROFILE: + if (fc->profile) + gst_encoding_profile_unref (fc->profile); + fc->profile = (GstEncodingProfile *) gst_value_dup_mini_object (value); + break; + case PROP_DURATION: + fc->duration = g_value_get_uint64 (value); + break; + case PROP_DURATION_TOLERANCE: + fc->duration_tolerance = g_value_get_uint64 (value); + break; + case PROP_FILE_SIZE: + fc->file_size = g_value_get_uint64 (value); + break; + case PROP_FILE_SIZE_TOLERANCE: + fc->file_size_tolerance = g_value_get_uint64 (value); + break; + case PROP_SEEKABLE: + fc->seekable = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_qa_file_checker_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstQaFileChecker *fc; + + fc = GST_QA_FILE_CHECKER_CAST (object); + + switch (prop_id) { + case PROP_RUNNER: + g_value_set_object (value, + gst_qa_reporter_get_runner (GST_QA_REPORTER (fc))); + break; + case PROP_URI: + g_value_set_string (value, fc->uri); + break; + case PROP_PROFILE: + gst_value_set_mini_object (value, GST_MINI_OBJECT_CAST (fc->profile)); + break; + case PROP_DURATION: + g_value_set_uint64 (value, fc->duration); + break; + case PROP_DURATION_TOLERANCE: + g_value_set_uint64 (value, fc->duration_tolerance); + break; + case PROP_FILE_SIZE: + g_value_set_uint64 (value, fc->file_size); + break; + case PROP_FILE_SIZE_TOLERANCE: + g_value_set_uint64 (value, fc->file_size_tolerance); + break; + case PROP_SEEKABLE: + g_value_set_boolean (value, fc->seekable); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +check_file_size (GstQaFileChecker * fc) +{ + GStatBuf statbuf; + gchar *filepath; + guint64 size = 0; + gboolean ret = TRUE; + + filepath = g_filename_from_uri (fc->uri, NULL, NULL); + if (!filepath) { + /* TODO is this an error */ + return FALSE; + } + + if (g_stat (filepath, &statbuf) == 0) { + size = statbuf.st_size; + } + + if (size == 0) { + /* TODO error */ + ret = FALSE; + } else if (size < fc->file_size - fc->file_size_tolerance || + size > fc->file_size + fc->file_size_tolerance) { + /* TODO error */ + ret = FALSE; + goto end; + } + +end: + g_free (filepath); + return ret; +} + +static gboolean +check_file_duration (GstQaFileChecker * fc, GstDiscovererInfo * info) +{ + GstClockTime real_duration; + + if (!GST_CLOCK_TIME_IS_VALID (fc->duration)) + return TRUE; + + real_duration = gst_discoverer_info_get_duration (info); + if (real_duration < fc->duration - fc->duration_tolerance || + real_duration > fc->duration + fc->duration_tolerance) { + /* TODO error */ + return FALSE; + } + return TRUE; +} + +static gboolean +check_seekable (GstQaFileChecker * fc, GstDiscovererInfo * info) +{ + gboolean real_seekable; + + real_seekable = gst_discoverer_info_get_seekable (info); + if (real_seekable != fc->seekable) { + /* TODO error */ + return FALSE; + } + return TRUE; +} + +static gboolean +check_encoding_profile (GstQaFileChecker * fc, GstDiscovererInfo * info) +{ + GstEncodingProfile *profile = fc->profile; + GstEncodingProfile *result_profile; + gboolean ret = TRUE; + + if (profile == NULL) + return TRUE; + + result_profile = gst_encoding_profile_from_discoverer (info); + + /* TODO doesn't do subtitle checks */ + if (!gst_encoding_profile_is_equal (result_profile, profile)) { + /* TODO error */ + ret = FALSE; + } + + gst_encoding_profile_unref (result_profile); + return ret; +} + + +gboolean +gst_qa_file_checker_run (GstQaFileChecker * fc) +{ + GError *err = NULL; + GstDiscovererInfo *info; + GstDiscoverer *discoverer = gst_discoverer_new (GST_SECOND * 60, &err); + gboolean ret = TRUE; + + if (!discoverer) { + /* TODO set error */ + return FALSE; + } + + info = gst_discoverer_discover_uri (discoverer, fc->uri, &err); + + if (gst_discoverer_info_get_result (info) != GST_DISCOVERER_OK) { + /* TODO error */ + return FALSE; + } + + ret = check_file_size (fc) & ret; + ret = check_file_duration (fc, info) & ret; + ret = check_seekable (fc, info) & ret; + ret = check_encoding_profile (fc, info) & ret; + + return ret; +} diff --git a/validate/gst/qa/gst-qa-file-checker.h b/validate/gst/qa/gst-qa-file-checker.h new file mode 100644 index 0000000000..d967760ef1 --- /dev/null +++ b/validate/gst/qa/gst-qa-file-checker.h @@ -0,0 +1,90 @@ +/* GStreamer + * Copyright (C) 2013 Thiago Santos + * + * gst-qa-file-checker.h - QA File conformance check utility functions / structs + * + * 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_FILE_CHECK_H__ +#define __GST_QA_FILE_CHECK_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_QA_FILE_CHECKER (gst_qa_file_checker_get_type ()) +#define GST_IS_QA_FILE_CHECKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_QA_FILE_CHECKER)) +#define GST_IS_QA_FILE_CHECKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_QA_FILE_CHECKER)) +#define GST_QA_FILE_CHECKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_QA_FILE_CHECKER, GstQaFileCheckerClass)) +#define GST_QA_FILE_CHECKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_QA_FILE_CHECKER, GstQaFileChecker)) +#define GST_QA_FILE_CHECKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_QA_FILE_CHECKER, GstQaFileCheckerClass)) +#define GST_QA_FILE_CHECKER_CAST(obj) ((GstQaFileChecker*)(obj)) +#define GST_QA_FILE_CHECKER_CLASS_CAST(klass) ((GstQaFileCheckerClass*)(klass)) + +typedef struct _GstQaFileChecker GstQaFileChecker; +typedef struct _GstQaFileCheckerClass GstQaFileCheckerClass; + +/** + * GstQaFileChecker: + * + * GStreamer QA FileChecker class. + * + * Class that wraps a #GObject for QA checks + */ +struct _GstQaFileChecker { + GObject object; + + /* */ + /* Value for the expected total duration of the file in nanosecs + * Set to GST_CLOCK_TIME_NONE if it shouldn't be tested */ + GstClockTime duration; + /* Acceptable tolerance for duration */ + GstClockTime duration_tolerance; + + /* Expected file_size, set to 0 to skip test */ + guint64 file_size; + /* Acceptable tolerance for file_size check */ + guint64 file_size_tolerance; + + gboolean seekable; /* TODO should we care about disabling this check? */ + + gchar *uri; + + /* Set to NULL to skip check */ + GstEncodingProfile *profile; +}; + +/** + * GstQaFileCheckerClass: + * @parent_class: parent + * + * GStreamer QA FileChecker object class. + */ +struct _GstQaFileCheckerClass { + GObjectClass parent_class; +}; + +/* normal GObject stuff */ +GType gst_qa_file_checker_get_type (void); + +gboolean gst_qa_file_checker_run (GstQaFileChecker * fc); + +G_END_DECLS + +#endif /* __GST_QA_FILE_CHECK_H__ */ + diff --git a/validate/gst/qa/gst-qa-transcoding.c b/validate/gst/qa/gst-qa-transcoding.c index 8fbc305f49..346d15a085 100644 --- a/validate/gst/qa/gst-qa-transcoding.c +++ b/validate/gst/qa/gst-qa-transcoding.c @@ -13,6 +13,8 @@ #include #include +#include "gst-qa-file-checker.h" + static GMainLoop *mainloop; static GstElement *pipeline; @@ -247,6 +249,7 @@ main (int argc, gchar ** argv) GError *err = NULL; const gchar *scenario = NULL; guint count = -1; + gboolean run_file_checks = FALSE; GOptionEntry options[] = { {"output-format", 'o', 0, G_OPTION_ARG_CALLBACK, &_parse_encoding_profile, @@ -261,6 +264,9 @@ main (int argc, gchar ** argv) {"set-scenario", '\0', 0, G_OPTION_ARG_STRING, &scenario, "Let you set a scenario, it will override the GST_QA_SCENARIO " "environment variable", NULL}, + {"run-file-checks", 'c', 0, G_OPTION_ARG_NONE, + &run_file_checks, "If post file transcoding checks should be run", + NULL}, {NULL} }; @@ -324,6 +330,18 @@ exit: g_main_loop_unref (mainloop); g_object_unref (runner); g_object_unref (pipeline); + + if (run_file_checks) { + GstQaFileChecker *fc = g_object_new (GST_TYPE_QA_FILE_CHECKER, "uri", + argv[2], "profile", encoding_profile, NULL); + + if (!gst_qa_file_checker_run (fc)) { + g_print ("Failed file checking\n"); + } + + g_object_unref (fc); + } + if (count) return -1; return 0;