summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/plugins/gst_plugins_cache.json36
-rw-r--r--gst/debugutils/debugutilsbad.c9
-rw-r--r--gst/debugutils/gstdebugutilsbadelements.h7
-rw-r--r--gst/debugutils/gstvideocodectestsink.c426
-rw-r--r--gst/debugutils/gstvideocodectestsink.h32
-rw-r--r--gst/debugutils/meson.build11
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,
)