2 * Copyright (C) 2021 Collabora Ltd.
3 * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
26 #include <gst/video/video.h>
29 #include "gstdebugutilsbadelements.h"
30 #include "gstvideocodectestsink.h"
33 * SECTION:videocodectestsink
35 * An element that computes the checksum of a video stream and/or writes back its
36 * raw I420 data ignoring the padding introduced by GStreamer. This element is
37 * meant to be used for CODEC conformance testing. It also supports producing an I420
38 * checksum and and can write out a file in I420 layout directly from NV12 input
41 * The checksum is communicated back to the application just before EOS
42 * message with an element message of type `conformance/checksum` with the
45 * * "checksum-type" G_TYPE_STRING The checksum type (only MD5 is supported)
46 * * "checksum" G_TYPE_STRING The checksum as a string
48 * ## Example launch lines
50 * gst-launch-1.0 videotestsrc num-buffers=2 ! videocodectestsink location=true-raw.yuv -m
62 struct _GstVideoCodecTestSink
67 /* protect with stream lock */
69 GstFlowReturn (*process) (GstVideoCodecTestSink * self,
70 GstVideoFrame * frame);
71 GOutputStream *ostream;
74 /* protect with object lock */
78 static GstStaticPadTemplate gst_video_codec_test_sink_template =
79 GST_STATIC_PAD_TEMPLATE ("sink",
82 GST_STATIC_CAPS ("video/x-raw, format = { "
83 "I422_10LE, I420_10LE, Y42B, I420, NV12 }"));
85 #define gst_video_codec_test_sink_parent_class parent_class
86 G_DEFINE_TYPE (GstVideoCodecTestSink, gst_video_codec_test_sink,
88 GST_ELEMENT_REGISTER_DEFINE (videocodectestsink, "videocodectestsink",
89 GST_RANK_NONE, gst_video_codec_test_sink_get_type ());
92 gst_video_codec_test_sink_set_property (GObject * object, guint prop_id,
93 const GValue * value, GParamSpec * pspec)
95 GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
97 GST_OBJECT_LOCK (self);
101 g_free (self->location);
102 self->location = g_value_dup_string (value);
105 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
109 GST_OBJECT_UNLOCK (self);
113 gst_video_codec_test_sink_get_property (GObject * object, guint prop_id,
114 GValue * value, GParamSpec * pspec)
116 GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
118 GST_OBJECT_LOCK (self);
122 g_value_set_string (value, self->location);
125 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
129 GST_OBJECT_UNLOCK (self);
133 gst_video_codec_test_sink_start (GstBaseSink * sink)
135 GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
136 GError *error = NULL;
140 GST_OBJECT_LOCK (self);
142 self->checksum = g_checksum_new (self->hash);
144 file = g_file_new_for_path (self->location);
146 GST_OBJECT_UNLOCK (self);
149 self->ostream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
150 G_FILE_CREATE_REPLACE_DESTINATION, NULL, &error));
151 if (!self->ostream) {
152 GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
153 ("Failed to open '%s' for writing.", self->location),
154 ("Open failed failed: %s", error->message));
155 g_error_free (error);
159 g_object_unref (file);
166 gst_video_codec_test_sink_stop (GstBaseSink * sink)
168 GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
170 g_checksum_free (self->checksum);
171 self->checksum = NULL;
174 GError *error = NULL;
176 if (!g_output_stream_close (self->ostream, NULL, &error)) {
177 GST_ELEMENT_WARNING (self, RESOURCE, CLOSE,
178 ("Did not close '%s' properly", self->location),
179 ("Failed to close stream: %s", error->message));
182 g_clear_object (&self->ostream);
189 gst_video_codec_test_sink_process_data (GstVideoCodecTestSink * self,
190 const guchar * data, gssize length)
192 GError *error = NULL;
194 g_checksum_update (self->checksum, data, length);
199 if (!g_output_stream_write_all (self->ostream, data, length, NULL, NULL,
201 GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
202 ("Failed to write video data into '%s'", self->location),
203 ("Writing %" G_GSIZE_FORMAT " bytes failed: %s", length,
205 g_error_free (error);
206 return GST_FLOW_ERROR;
213 gst_video_codec_test_sink_process_i42x (GstVideoCodecTestSink * self,
214 GstVideoFrame * frame)
218 for (plane = 0; plane < 3; plane++) {
223 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, plane);
224 data = GST_VIDEO_FRAME_PLANE_DATA (frame, plane);
226 for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, plane); y++) {
227 gsize length = GST_VIDEO_INFO_COMP_WIDTH (&self->vinfo, plane) *
228 GST_VIDEO_INFO_COMP_PSTRIDE (&self->vinfo, plane);
231 ret = gst_video_codec_test_sink_process_data (self, data, length);
232 if (ret != GST_FLOW_OK)
243 gst_video_codec_test_sink_process_nv12 (GstVideoCodecTestSink * self,
244 GstVideoFrame * frame)
250 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
251 data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
253 for (y = 0; y < GST_VIDEO_INFO_HEIGHT (&self->vinfo); y++) {
254 gsize length = GST_VIDEO_INFO_WIDTH (&self->vinfo);
257 ret = gst_video_codec_test_sink_process_data (self, data, length);
258 if (ret != GST_FLOW_OK)
264 /* Deinterleave the UV plane */
265 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 1);
267 for (comp = 0; comp < 2; comp++) {
268 data = GST_VIDEO_FRAME_PLANE_DATA (frame, 1);
270 for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, 1); y++) {
271 guint width = GST_ROUND_UP_2 (GST_VIDEO_INFO_WIDTH (&self->vinfo)) / 2;
273 for (x = 0; x < width; x++) {
276 ret = gst_video_codec_test_sink_process_data (self,
277 data + 2 * x + comp, 1);
279 if (ret != GST_FLOW_OK)
291 gst_video_codec_test_sink_render (GstBaseSink * sink, GstBuffer * buffer)
293 GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
296 if (!gst_video_frame_map (&frame, &self->vinfo, buffer, GST_MAP_READ))
297 return GST_FLOW_ERROR;
299 self->process (self, &frame);
301 gst_video_frame_unmap (&frame);
306 gst_video_codec_test_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
308 GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
310 if (!gst_video_info_from_caps (&self->vinfo, caps))
313 switch (GST_VIDEO_INFO_FORMAT (&self->vinfo)) {
314 case GST_VIDEO_FORMAT_I420:
315 case GST_VIDEO_FORMAT_I420_10LE:
316 case GST_VIDEO_FORMAT_Y42B:
317 case GST_VIDEO_FORMAT_I422_10LE:
318 self->process = gst_video_codec_test_sink_process_i42x;
320 case GST_VIDEO_FORMAT_NV12:
321 self->process = gst_video_codec_test_sink_process_nv12;
324 g_assert_not_reached ();
332 gst_video_codec_test_sink_propose_allocation (GstBaseSink * sink,
335 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
340 gst_video_codec_test_sink_event (GstBaseSink * sink, GstEvent * event)
342 GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
344 if (event->type == GST_EVENT_EOS) {
345 const gchar *checksum_type = "UNKNOWN";
347 switch (self->hash) {
349 checksum_type = "MD5";
351 case G_CHECKSUM_SHA1:
352 checksum_type = "SHA1";
354 case G_CHECKSUM_SHA256:
355 checksum_type = "SHA256";
357 case G_CHECKSUM_SHA512:
358 checksum_type = "SHA512";
360 case G_CHECKSUM_SHA384:
361 checksum_type = "SHA384";
364 g_assert_not_reached ();
368 gst_element_post_message (GST_ELEMENT (self),
369 gst_message_new_element (GST_OBJECT (self),
370 gst_structure_new ("conformance/checksum", "checksum-type",
371 G_TYPE_STRING, checksum_type, "checksum", G_TYPE_STRING,
372 g_checksum_get_string (self->checksum), NULL)));
373 g_checksum_reset (self->checksum);
376 return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
380 gst_video_codec_test_sink_init (GstVideoCodecTestSink * sink)
382 gst_base_sink_set_sync (GST_BASE_SINK (sink), FALSE);
383 sink->hash = G_CHECKSUM_MD5;
387 gst_video_codec_test_sink_finalize (GObject * object)
389 GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
391 g_free (self->location);
393 G_OBJECT_CLASS (parent_class)->finalize (object);
397 gst_video_codec_test_sink_class_init (GstVideoCodecTestSinkClass * klass)
399 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
400 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
401 GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass);
403 gobject_class->set_property = gst_video_codec_test_sink_set_property;
404 gobject_class->get_property = gst_video_codec_test_sink_get_property;
405 gobject_class->finalize = gst_video_codec_test_sink_finalize;
407 base_sink_class->start = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_start);
408 base_sink_class->stop = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_stop);
409 base_sink_class->render =
410 GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_render);
411 base_sink_class->set_caps =
412 GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_set_caps);
413 base_sink_class->propose_allocation =
414 GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_propose_allocation);
415 base_sink_class->event = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_event);
417 gst_element_class_add_static_pad_template (element_class,
418 &gst_video_codec_test_sink_template);
420 g_object_class_install_property (gobject_class, PROP_LOCATION,
421 g_param_spec_string ("location", "Location",
422 "File path to store non-padded I420 stream (optional).", NULL,
423 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
425 gst_element_class_set_static_metadata (element_class,
426 "Video CODEC Test Sink", "Debug/video/Sink",
427 "Sink to test video CODEC conformance",
428 "Nicolas Dufresne <nicolas.dufresne@collabora.com");