diff options
-rw-r--r-- | docs/plugins/gst_plugins_cache.json | 36 | ||||
-rw-r--r-- | gst/debugutils/debugutilsbad.c | 9 | ||||
-rw-r--r-- | gst/debugutils/gstdebugutilsbadelements.h | 7 | ||||
-rw-r--r-- | gst/debugutils/gstvideocodectestsink.c | 426 | ||||
-rw-r--r-- | gst/debugutils/gstvideocodectestsink.h | 32 | ||||
-rw-r--r-- | gst/debugutils/meson.build | 11 |
6 files changed, 509 insertions, 12 deletions
diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index fe3f21bf9..77b2eb416 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -9184,6 +9184,42 @@ "rank": "none", "signals": {} }, + "videocodectestsink": { + "author": "Nicolas Dufresne <nicolas.dufresne@collabora.com", + "description": "Sink to test video CODEC conformance", + "hierarchy": [ + "GstVideoCodecTestSink", + "GstBaseSink", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Debug/video/Sink", + "long-name": "Video CODEC Test Sink", + "pad-templates": { + "sink": { + "caps": "video/x-raw:\n format: { I420, I420_10LE, NV12 }\n", + "direction": "sink", + "presence": "always" + } + }, + "properties": { + "location": { + "blurb": "File path to store non-padded I420 stream (optional).", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + } + }, + "rank": "none" + }, "watchdog": { "author": "David Schleef <ds@schleef.org>", "description": "Watches for pauses in stream buffers", diff --git a/gst/debugutils/debugutilsbad.c b/gst/debugutils/debugutilsbad.c index 29376a3d9..f5a3529da 100644 --- a/gst/debugutils/debugutilsbad.c +++ b/gst/debugutils/debugutilsbad.c @@ -32,16 +32,17 @@ plugin_init (GstPlugin * plugin) gboolean ret = FALSE; ret |= GST_ELEMENT_REGISTER (checksumsink, plugin); - ret |= GST_ELEMENT_REGISTER (fpsdisplaysink, plugin); ret |= GST_ELEMENT_REGISTER (chopmydata, plugin); + ret |= GST_ELEMENT_REGISTER (clockselect, plugin); ret |= GST_ELEMENT_REGISTER (compare, plugin); ret |= GST_ELEMENT_REGISTER (debugspy, plugin); - ret |= GST_ELEMENT_REGISTER (watchdog, plugin); ret |= GST_ELEMENT_REGISTER (errorignore, plugin); - ret |= GST_ELEMENT_REGISTER (fakevideosink, plugin); ret |= GST_ELEMENT_REGISTER (fakeaudiosink, plugin); + ret |= GST_ELEMENT_REGISTER (fakevideosink, plugin); + ret |= GST_ELEMENT_REGISTER (fpsdisplaysink, plugin); ret |= GST_ELEMENT_REGISTER (testsrcbin, plugin); - ret |= GST_ELEMENT_REGISTER (clockselect, plugin); + ret |= GST_ELEMENT_REGISTER (videocodectestsink, plugin); + ret |= GST_ELEMENT_REGISTER (watchdog, plugin); return ret; } diff --git a/gst/debugutils/gstdebugutilsbadelements.h b/gst/debugutils/gstdebugutilsbadelements.h index 71a42cbe1..4a4932feb 100644 --- a/gst/debugutils/gstdebugutilsbadelements.h +++ b/gst/debugutils/gstdebugutilsbadelements.h @@ -27,16 +27,17 @@ #include <gst/gst.h> -GST_ELEMENT_REGISTER_DECLARE (fpsdisplaysink); GST_ELEMENT_REGISTER_DECLARE (checksumsink); GST_ELEMENT_REGISTER_DECLARE (chopmydata); GST_ELEMENT_REGISTER_DECLARE (clockselect); GST_ELEMENT_REGISTER_DECLARE (compare); GST_ELEMENT_REGISTER_DECLARE (debugspy); -GST_ELEMENT_REGISTER_DECLARE (testsrcbin); GST_ELEMENT_REGISTER_DECLARE (errorignore); -GST_ELEMENT_REGISTER_DECLARE (fakevideosink); GST_ELEMENT_REGISTER_DECLARE (fakeaudiosink); +GST_ELEMENT_REGISTER_DECLARE (fakevideosink); +GST_ELEMENT_REGISTER_DECLARE (fpsdisplaysink); +GST_ELEMENT_REGISTER_DECLARE (testsrcbin); +GST_ELEMENT_REGISTER_DECLARE (videocodectestsink); GST_ELEMENT_REGISTER_DECLARE (watchdog); diff --git a/gst/debugutils/gstvideocodectestsink.c b/gst/debugutils/gstvideocodectestsink.c new file mode 100644 index 000000000..127106a7d --- /dev/null +++ b/gst/debugutils/gstvideocodectestsink.c @@ -0,0 +1,426 @@ +/* GStreamer + * Copyright (C) 2021 Collabora Ltd. + * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> + * + * 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 <gst/gst.h> +#include <gst/video/video.h> +#include <gio/gio.h> + +#include "gstdebugutilsbadelements.h" +#include "gstvideocodectestsink.h" + +/** + * SECTION:videocodectestsink + * + * An element that computes the checksum of a video stream and/or writes back its + * raw I420 data ignoring the padding introduced by GStreamer. This element is + * meant to be used for CODEC conformance testing. It also supports producing an I420 + * checksum and and can write out a file in I420 layout directly from NV12 input + * data. + * + * The checksum is communicated back to the application just before EOS + * message with an element message of type `conformance/checksum` with the + * following fields: + * + * * "checksum-type" G_TYPE_STRING The checksum type (only MD5 is supported) + * * "checksum" G_TYPE_STRING The checksum as a string + * + * ## Example launch lines + * |[ + * gst-launch-1.0 videotestsrc num-buffers=2 ! videocodectestsink location=true-raw.yuv -m + * ]| + * + * Since: 1.20 + */ + +enum +{ + PROP_0, + PROP_LOCATION, +}; + +struct _GstVideoCodecTestSink +{ + GstBaseSink parent; + GChecksumType hash; + + /* protect with stream lock */ + GstVideoInfo vinfo; + GstFlowReturn (*process) (GstVideoCodecTestSink * self, + GstVideoFrame * frame); + GOutputStream *ostream; + GChecksum *checksum; + + /* protect with object lock */ + gchar *location; +}; + +static GstStaticPadTemplate gst_video_codec_test_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw, format = { I420, I420_10LE, NV12 }")); + +#define gst_video_codec_test_sink_parent_class parent_class +G_DEFINE_TYPE (GstVideoCodecTestSink, gst_video_codec_test_sink, + GST_TYPE_BASE_SINK); +GST_ELEMENT_REGISTER_DEFINE (videocodectestsink, "videocodectestsink", + GST_RANK_NONE, gst_video_codec_test_sink_get_type ()); + +static void +gst_video_codec_test_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object); + + GST_OBJECT_LOCK (self); + + switch (prop_id) { + case PROP_LOCATION: + g_free (self->location); + self->location = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + GST_OBJECT_UNLOCK (self); +} + +static void +gst_video_codec_test_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object); + + GST_OBJECT_LOCK (self); + + switch (prop_id) { + case PROP_LOCATION: + g_value_set_string (value, self->location); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + GST_OBJECT_UNLOCK (self); +} + +static gboolean +gst_video_codec_test_sink_start (GstBaseSink * sink) +{ + GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink); + GError *error = NULL; + GFile *file = NULL; + gboolean ret = TRUE; + + GST_OBJECT_LOCK (self); + + self->checksum = g_checksum_new (self->hash); + if (self->location) + file = g_file_new_for_path (self->location); + + GST_OBJECT_UNLOCK (self); + + if (file) { + self->ostream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, NULL, &error)); + if (!self->ostream) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to open '%s' for writing.", self->location), + ("Open failed failed: %s", error->message)); + g_error_free (error); + ret = FALSE; + } + + g_object_unref (file); + } + + return ret; +} + +static gboolean +gst_video_codec_test_sink_stop (GstBaseSink * sink) +{ + GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink); + + g_checksum_free (self->checksum); + self->checksum = NULL; + + if (self->ostream) { + GError *error = NULL; + + if (!g_output_stream_close (self->ostream, NULL, &error)) { + GST_ELEMENT_WARNING (self, RESOURCE, CLOSE, + ("Did not close '%s' properly", self->location), + ("Failed to close stream: %s", error->message)); + } + + g_clear_object (&self->ostream); + } + + return TRUE; +} + +static GstFlowReturn +gst_video_codec_test_sink_process_data (GstVideoCodecTestSink * self, + const guchar * data, gssize length) +{ + GError *error = NULL; + + g_checksum_update (self->checksum, data, length); + + if (!self->ostream) + return GST_FLOW_OK; + + if (!g_output_stream_write_all (self->ostream, data, length, NULL, NULL, + &error)) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to write video data into '%s'", self->location), + ("Writing %" G_GSIZE_FORMAT " bytes failed: %s", length, + error->message)); + g_error_free (error); + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_video_codec_test_sink_process_i420 (GstVideoCodecTestSink * self, + GstVideoFrame * frame) +{ + guint plane; + + for (plane = 0; plane < 3; plane++) { + gint y; + guint stride; + const guchar *data; + + stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, plane); + data = GST_VIDEO_FRAME_PLANE_DATA (frame, plane); + + for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, plane); y++) { + gsize length = GST_VIDEO_INFO_COMP_WIDTH (&self->vinfo, plane) * + GST_VIDEO_INFO_COMP_PSTRIDE (&self->vinfo, plane); + GstFlowReturn ret; + + ret = gst_video_codec_test_sink_process_data (self, data, length); + if (ret != GST_FLOW_OK) + return ret; + + data += stride; + } + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_video_codec_test_sink_process_nv12 (GstVideoCodecTestSink * self, + GstVideoFrame * frame) +{ + gint x, y, comp; + guint stride; + const guchar *data; + + stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0); + data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); + + for (y = 0; y < GST_VIDEO_INFO_HEIGHT (&self->vinfo); y++) { + gsize length = GST_VIDEO_INFO_WIDTH (&self->vinfo); + GstFlowReturn ret; + + ret = gst_video_codec_test_sink_process_data (self, data, length); + if (ret != GST_FLOW_OK) + return ret; + + data += stride; + } + + /* Deinterleave the UV plane */ + stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 1); + + for (comp = 0; comp < 2; comp++) { + data = GST_VIDEO_FRAME_PLANE_DATA (frame, 1); + + for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, 1); y++) { + guint width = GST_ROUND_UP_2 (GST_VIDEO_INFO_WIDTH (&self->vinfo)) / 2; + + for (x = 0; x < width; x++) { + GstFlowReturn ret; + + ret = gst_video_codec_test_sink_process_data (self, + data + 2 * x + comp, 1); + + if (ret != GST_FLOW_OK) + return ret; + } + + data += stride; + } + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_video_codec_test_sink_render (GstBaseSink * sink, GstBuffer * buffer) +{ + GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink); + GstVideoFrame frame; + + if (!gst_video_frame_map (&frame, &self->vinfo, buffer, GST_MAP_READ)) + return GST_FLOW_ERROR; + + self->process (self, &frame); + + gst_video_frame_unmap (&frame); + return GST_FLOW_OK; +} + +static gboolean +gst_video_codec_test_sink_set_caps (GstBaseSink * sink, GstCaps * caps) +{ + GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink); + + if (!gst_video_info_from_caps (&self->vinfo, caps)) + return FALSE; + + switch (GST_VIDEO_INFO_FORMAT (&self->vinfo)) { + case GST_VIDEO_FORMAT_I420: + case GST_VIDEO_FORMAT_I420_10LE: + self->process = gst_video_codec_test_sink_process_i420; + break; + case GST_VIDEO_FORMAT_NV12: + self->process = gst_video_codec_test_sink_process_nv12; + break; + default: + g_assert_not_reached (); + break; + } + + return TRUE; +} + +static gboolean +gst_video_codec_test_sink_propose_allocation (GstBaseSink * sink, + GstQuery * query) +{ + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + return TRUE; +} + +static gboolean +gst_video_codec_test_sink_event (GstBaseSink * sink, GstEvent * event) +{ + GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink); + + if (event->type == GST_EVENT_EOS) { + const gchar *checksum_type = "UNKNOWN"; + + switch (self->hash) { + case G_CHECKSUM_MD5: + checksum_type = "MD5"; + break; + case G_CHECKSUM_SHA1: + checksum_type = "SHA1"; + break; + case G_CHECKSUM_SHA256: + checksum_type = "SHA256"; + break; + case G_CHECKSUM_SHA512: + checksum_type = "SHA512"; + break; + case G_CHECKSUM_SHA384: + checksum_type = "SHA384"; + break; + default: + g_assert_not_reached (); + break; + } + + gst_element_post_message (GST_ELEMENT (self), + gst_message_new_element (GST_OBJECT (self), + gst_structure_new ("conformance/checksum", "checksum-type", + G_TYPE_STRING, checksum_type, "checksum", G_TYPE_STRING, + g_checksum_get_string (self->checksum), NULL))); + g_checksum_reset (self->checksum); + } + + return GST_BASE_SINK_CLASS (parent_class)->event (sink, event); +} + +static void +gst_video_codec_test_sink_init (GstVideoCodecTestSink * sink) +{ + gst_base_sink_set_sync (GST_BASE_SINK (sink), FALSE); + sink->hash = G_CHECKSUM_MD5; +} + +static void +gst_video_codec_test_sink_finalize (GObject * object) +{ + GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object); + + g_free (self->location); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_video_codec_test_sink_class_init (GstVideoCodecTestSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass); + + gobject_class->set_property = gst_video_codec_test_sink_set_property; + gobject_class->get_property = gst_video_codec_test_sink_get_property; + gobject_class->finalize = gst_video_codec_test_sink_finalize; + + base_sink_class->start = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_start); + base_sink_class->stop = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_stop); + base_sink_class->render = + GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_render); + base_sink_class->set_caps = + GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_set_caps); + base_sink_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_propose_allocation); + base_sink_class->event = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_event); + + gst_element_class_add_static_pad_template (element_class, + &gst_video_codec_test_sink_template); + + g_object_class_install_property (gobject_class, PROP_LOCATION, + g_param_spec_string ("location", "Location", + "File path to store non-padded I420 stream (optional).", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_set_static_metadata (element_class, + "Video CODEC Test Sink", "Debug/video/Sink", + "Sink to test video CODEC conformance", + "Nicolas Dufresne <nicolas.dufresne@collabora.com"); +} diff --git a/gst/debugutils/gstvideocodectestsink.h b/gst/debugutils/gstvideocodectestsink.h new file mode 100644 index 000000000..df9c4328f --- /dev/null +++ b/gst/debugutils/gstvideocodectestsink.h @@ -0,0 +1,32 @@ +/* GStreamer + * Copyright (C) 2021 Collabora Ltd. + * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> + * + * 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. + */ + +#pragma once + +#include <gst/gst.h> +#include <gst/base/gstbasesink.h> + +G_BEGIN_DECLS + +#define GST_TYPE_VIDEO_CODEC_TEST_SINK gst_video_codec_test_sink_get_type () +G_DECLARE_FINAL_TYPE (GstVideoCodecTestSink, gst_video_codec_test_sink, GST, + VIDEO_CODEC_TEST_SINK, GstBaseSink); + +G_END_DECLS diff --git a/gst/debugutils/meson.build b/gst/debugutils/meson.build index d05161195..9af3578cd 100644 --- a/gst/debugutils/meson.build +++ b/gst/debugutils/meson.build @@ -1,23 +1,24 @@ debugutilsbad_sources = [ - 'gstdebugspy.c', - 'gsterrorignore.c', 'debugutilsbad.c', 'fpsdisplaysink.c', 'gstchecksumsink.c', 'gstchopmydata.c', + 'gstclockselect.c', 'gstcompare.c', + 'gstdebugspy.c', + 'gsterrorignore.c', 'gstfakeaudiosink.c', 'gstfakevideosink.c', - 'gstwatchdog.c', 'gsttestsrcbin.c', - 'gstclockselect.c', + 'gstvideocodectestsink.c', + 'gstwatchdog.c', ] gstdebugutilsbad = library('gstdebugutilsbad', debugutilsbad_sources, c_args : gst_plugins_bad_args, include_directories : [configinc], - dependencies : [gstbase_dep, gstvideo_dep, gstnet_dep, gstaudio_dep], + dependencies : [gstbase_dep, gstvideo_dep, gstnet_dep, gstaudio_dep, gio_dep], install : true, install_dir : plugins_install_dir, ) |