• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2021 Collabora Ltd.
3  *   Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
4  *
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.
9  *
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.
14  *
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.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include <gst/gst.h>
26 #include <gst/video/video.h>
27 #include <gio/gio.h>
28 
29 #include "gstdebugutilsbadelements.h"
30 #include "gstvideocodectestsink.h"
31 
32 /**
33  * SECTION:videocodectestsink
34  *
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
39  * data.
40  *
41  * The checksum is communicated back to the application just before EOS
42  * message with an element message of type `conformance/checksum` with the
43  * following fields:
44  *
45  * * "checksum-type"  G_TYPE_STRING The checksum type (only MD5 is supported)
46  * * "checksum"       G_TYPE_STRING The checksum as a string
47  *
48  * ## Example launch lines
49  * |[
50  * gst-launch-1.0 videotestsrc num-buffers=2 ! videocodectestsink location=true-raw.yuv -m
51  * ]|
52  *
53  * Since: 1.20
54  */
55 
56 enum
57 {
58   PROP_0,
59   PROP_LOCATION,
60 };
61 
62 struct _GstVideoCodecTestSink
63 {
64   GstBaseSink parent;
65   GChecksumType hash;
66 
67   /* protect with stream lock */
68   GstVideoInfo vinfo;
69     GstFlowReturn (*process) (GstVideoCodecTestSink * self,
70       GstVideoFrame * frame);
71   GOutputStream *ostream;
72   GChecksum *checksum;
73 
74   /* protect with object lock */
75   gchar *location;
76 };
77 
78 static GstStaticPadTemplate gst_video_codec_test_sink_template =
79 GST_STATIC_PAD_TEMPLATE ("sink",
80     GST_PAD_SINK,
81     GST_PAD_ALWAYS,
82     GST_STATIC_CAPS ("video/x-raw, format = { I420, I420_10LE, NV12 }"));
83 
84 #define gst_video_codec_test_sink_parent_class parent_class
85 G_DEFINE_TYPE (GstVideoCodecTestSink, gst_video_codec_test_sink,
86     GST_TYPE_BASE_SINK);
87 GST_ELEMENT_REGISTER_DEFINE (videocodectestsink, "videocodectestsink",
88     GST_RANK_NONE, gst_video_codec_test_sink_get_type ());
89 
90 static void
gst_video_codec_test_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)91 gst_video_codec_test_sink_set_property (GObject * object, guint prop_id,
92     const GValue * value, GParamSpec * pspec)
93 {
94   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
95 
96   GST_OBJECT_LOCK (self);
97 
98   switch (prop_id) {
99     case PROP_LOCATION:
100       g_free (self->location);
101       self->location = g_value_dup_string (value);
102       break;
103     default:
104       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
105       break;
106   }
107 
108   GST_OBJECT_UNLOCK (self);
109 }
110 
111 static void
gst_video_codec_test_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)112 gst_video_codec_test_sink_get_property (GObject * object, guint prop_id,
113     GValue * value, GParamSpec * pspec)
114 {
115   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
116 
117   GST_OBJECT_LOCK (self);
118 
119   switch (prop_id) {
120     case PROP_LOCATION:
121       g_value_set_string (value, self->location);
122       break;
123     default:
124       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
125       break;
126   }
127 
128   GST_OBJECT_UNLOCK (self);
129 }
130 
131 static gboolean
gst_video_codec_test_sink_start(GstBaseSink * sink)132 gst_video_codec_test_sink_start (GstBaseSink * sink)
133 {
134   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
135   GError *error = NULL;
136   GFile *file = NULL;
137   gboolean ret = TRUE;
138 
139   GST_OBJECT_LOCK (self);
140 
141   self->checksum = g_checksum_new (self->hash);
142   if (self->location)
143     file = g_file_new_for_path (self->location);
144 
145   GST_OBJECT_UNLOCK (self);
146 
147   if (file) {
148     self->ostream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
149             G_FILE_CREATE_REPLACE_DESTINATION, NULL, &error));
150     if (!self->ostream) {
151       GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
152           ("Failed to open '%s' for writing.", self->location),
153           ("Open failed failed: %s", error->message));
154       g_error_free (error);
155       ret = FALSE;
156     }
157 
158     g_object_unref (file);
159   }
160 
161   return ret;
162 }
163 
164 static gboolean
gst_video_codec_test_sink_stop(GstBaseSink * sink)165 gst_video_codec_test_sink_stop (GstBaseSink * sink)
166 {
167   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
168 
169   g_checksum_free (self->checksum);
170   self->checksum = NULL;
171 
172   if (self->ostream) {
173     GError *error = NULL;
174 
175     if (!g_output_stream_close (self->ostream, NULL, &error)) {
176       GST_ELEMENT_WARNING (self, RESOURCE, CLOSE,
177           ("Did not close '%s' properly", self->location),
178           ("Failed to close stream: %s", error->message));
179     }
180 
181     g_clear_object (&self->ostream);
182   }
183 
184   return TRUE;
185 }
186 
187 static GstFlowReturn
gst_video_codec_test_sink_process_data(GstVideoCodecTestSink * self,const guchar * data,gssize length)188 gst_video_codec_test_sink_process_data (GstVideoCodecTestSink * self,
189     const guchar * data, gssize length)
190 {
191   GError *error = NULL;
192 
193   g_checksum_update (self->checksum, data, length);
194 
195   if (!self->ostream)
196     return GST_FLOW_OK;
197 
198   if (!g_output_stream_write_all (self->ostream, data, length, NULL, NULL,
199           &error)) {
200     GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
201         ("Failed to write video data into '%s'", self->location),
202         ("Writing %" G_GSIZE_FORMAT " bytes failed: %s", length,
203             error->message));
204     g_error_free (error);
205     return GST_FLOW_ERROR;
206   }
207 
208   return GST_FLOW_OK;
209 }
210 
211 static GstFlowReturn
gst_video_codec_test_sink_process_i420(GstVideoCodecTestSink * self,GstVideoFrame * frame)212 gst_video_codec_test_sink_process_i420 (GstVideoCodecTestSink * self,
213     GstVideoFrame * frame)
214 {
215   guint plane;
216 
217   for (plane = 0; plane < 3; plane++) {
218     gint y;
219     guint stride;
220     const guchar *data;
221 
222     stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, plane);
223     data = GST_VIDEO_FRAME_PLANE_DATA (frame, plane);
224 
225     for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, plane); y++) {
226       gsize length = GST_VIDEO_INFO_COMP_WIDTH (&self->vinfo, plane) *
227           GST_VIDEO_INFO_COMP_PSTRIDE (&self->vinfo, plane);
228       GstFlowReturn ret;
229 
230       ret = gst_video_codec_test_sink_process_data (self, data, length);
231       if (ret != GST_FLOW_OK)
232         return ret;
233 
234       data += stride;
235     }
236   }
237 
238   return GST_FLOW_OK;
239 }
240 
241 static GstFlowReturn
gst_video_codec_test_sink_process_nv12(GstVideoCodecTestSink * self,GstVideoFrame * frame)242 gst_video_codec_test_sink_process_nv12 (GstVideoCodecTestSink * self,
243     GstVideoFrame * frame)
244 {
245   gint x, y, comp;
246   guint stride;
247   const guchar *data;
248 
249   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
250   data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
251 
252   for (y = 0; y < GST_VIDEO_INFO_HEIGHT (&self->vinfo); y++) {
253     gsize length = GST_VIDEO_INFO_WIDTH (&self->vinfo);
254     GstFlowReturn ret;
255 
256     ret = gst_video_codec_test_sink_process_data (self, data, length);
257     if (ret != GST_FLOW_OK)
258       return ret;
259 
260     data += stride;
261   }
262 
263   /* Deinterleave the UV plane */
264   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 1);
265 
266   for (comp = 0; comp < 2; comp++) {
267     data = GST_VIDEO_FRAME_PLANE_DATA (frame, 1);
268 
269     for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, 1); y++) {
270       guint width = GST_ROUND_UP_2 (GST_VIDEO_INFO_WIDTH (&self->vinfo)) / 2;
271 
272       for (x = 0; x < width; x++) {
273         GstFlowReturn ret;
274 
275         ret = gst_video_codec_test_sink_process_data (self,
276             data + 2 * x + comp, 1);
277 
278         if (ret != GST_FLOW_OK)
279           return ret;
280       }
281 
282       data += stride;
283     }
284   }
285 
286   return GST_FLOW_OK;
287 }
288 
289 static GstFlowReturn
gst_video_codec_test_sink_render(GstBaseSink * sink,GstBuffer * buffer)290 gst_video_codec_test_sink_render (GstBaseSink * sink, GstBuffer * buffer)
291 {
292   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
293   GstVideoFrame frame;
294 
295   if (!gst_video_frame_map (&frame, &self->vinfo, buffer, GST_MAP_READ))
296     return GST_FLOW_ERROR;
297 
298   self->process (self, &frame);
299 
300   gst_video_frame_unmap (&frame);
301   return GST_FLOW_OK;
302 }
303 
304 static gboolean
gst_video_codec_test_sink_set_caps(GstBaseSink * sink,GstCaps * caps)305 gst_video_codec_test_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
306 {
307   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
308 
309   if (!gst_video_info_from_caps (&self->vinfo, caps))
310     return FALSE;
311 
312   switch (GST_VIDEO_INFO_FORMAT (&self->vinfo)) {
313     case GST_VIDEO_FORMAT_I420:
314     case GST_VIDEO_FORMAT_I420_10LE:
315       self->process = gst_video_codec_test_sink_process_i420;
316       break;
317     case GST_VIDEO_FORMAT_NV12:
318       self->process = gst_video_codec_test_sink_process_nv12;
319       break;
320     default:
321       g_assert_not_reached ();
322       break;
323   }
324 
325   return TRUE;
326 }
327 
328 static gboolean
gst_video_codec_test_sink_propose_allocation(GstBaseSink * sink,GstQuery * query)329 gst_video_codec_test_sink_propose_allocation (GstBaseSink * sink,
330     GstQuery * query)
331 {
332   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
333   return TRUE;
334 }
335 
336 static gboolean
gst_video_codec_test_sink_event(GstBaseSink * sink,GstEvent * event)337 gst_video_codec_test_sink_event (GstBaseSink * sink, GstEvent * event)
338 {
339   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
340 
341   if (event->type == GST_EVENT_EOS) {
342     const gchar *checksum_type = "UNKNOWN";
343 
344     switch (self->hash) {
345       case G_CHECKSUM_MD5:
346         checksum_type = "MD5";
347         break;
348       case G_CHECKSUM_SHA1:
349         checksum_type = "SHA1";
350         break;
351       case G_CHECKSUM_SHA256:
352         checksum_type = "SHA256";
353         break;
354       case G_CHECKSUM_SHA512:
355         checksum_type = "SHA512";
356         break;
357       case G_CHECKSUM_SHA384:
358         checksum_type = "SHA384";
359         break;
360       default:
361         g_assert_not_reached ();
362         break;
363     }
364 
365     gst_element_post_message (GST_ELEMENT (self),
366         gst_message_new_element (GST_OBJECT (self),
367             gst_structure_new ("conformance/checksum", "checksum-type",
368                 G_TYPE_STRING, checksum_type, "checksum", G_TYPE_STRING,
369                 g_checksum_get_string (self->checksum), NULL)));
370     g_checksum_reset (self->checksum);
371   }
372 
373   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
374 }
375 
376 static void
gst_video_codec_test_sink_init(GstVideoCodecTestSink * sink)377 gst_video_codec_test_sink_init (GstVideoCodecTestSink * sink)
378 {
379   gst_base_sink_set_sync (GST_BASE_SINK (sink), FALSE);
380   sink->hash = G_CHECKSUM_MD5;
381 }
382 
383 static void
gst_video_codec_test_sink_finalize(GObject * object)384 gst_video_codec_test_sink_finalize (GObject * object)
385 {
386   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
387 
388   g_free (self->location);
389 
390   G_OBJECT_CLASS (parent_class)->finalize (object);
391 }
392 
393 static void
gst_video_codec_test_sink_class_init(GstVideoCodecTestSinkClass * klass)394 gst_video_codec_test_sink_class_init (GstVideoCodecTestSinkClass * klass)
395 {
396   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
397   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
398   GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass);
399 
400   gobject_class->set_property = gst_video_codec_test_sink_set_property;
401   gobject_class->get_property = gst_video_codec_test_sink_get_property;
402   gobject_class->finalize = gst_video_codec_test_sink_finalize;
403 
404   base_sink_class->start = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_start);
405   base_sink_class->stop = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_stop);
406   base_sink_class->render =
407       GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_render);
408   base_sink_class->set_caps =
409       GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_set_caps);
410   base_sink_class->propose_allocation =
411       GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_propose_allocation);
412   base_sink_class->event = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_event);
413 
414   gst_element_class_add_static_pad_template (element_class,
415       &gst_video_codec_test_sink_template);
416 
417   g_object_class_install_property (gobject_class, PROP_LOCATION,
418       g_param_spec_string ("location", "Location",
419           "File path to store non-padded I420 stream (optional).", NULL,
420           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
421 
422   gst_element_class_set_static_metadata (element_class,
423       "Video CODEC Test Sink", "Debug/video/Sink",
424       "Sink to test video CODEC conformance",
425       "Nicolas Dufresne <nicolas.dufresne@collabora.com");
426 }
427