webrtc/nice: support consent-freshness RFC7675
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / gst / debugutils / gstvideocodectestsink.c
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 = { "
83         "Y444_12LE, I422_12LE, I420_12LE,"
84         "Y444_10LE, I422_10LE, I420_10LE, Y444, Y42B, I420, NV12 }"));
85
86 #define gst_video_codec_test_sink_parent_class parent_class
87 G_DEFINE_TYPE (GstVideoCodecTestSink, gst_video_codec_test_sink,
88     GST_TYPE_BASE_SINK);
89 GST_ELEMENT_REGISTER_DEFINE (videocodectestsink, "videocodectestsink",
90     GST_RANK_NONE, gst_video_codec_test_sink_get_type ());
91
92 static void
93 gst_video_codec_test_sink_set_property (GObject * object, guint prop_id,
94     const GValue * value, GParamSpec * pspec)
95 {
96   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
97
98   GST_OBJECT_LOCK (self);
99
100   switch (prop_id) {
101     case PROP_LOCATION:
102       g_free (self->location);
103       self->location = g_value_dup_string (value);
104       break;
105     default:
106       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
107       break;
108   }
109
110   GST_OBJECT_UNLOCK (self);
111 }
112
113 static void
114 gst_video_codec_test_sink_get_property (GObject * object, guint prop_id,
115     GValue * value, GParamSpec * pspec)
116 {
117   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
118
119   GST_OBJECT_LOCK (self);
120
121   switch (prop_id) {
122     case PROP_LOCATION:
123       g_value_set_string (value, self->location);
124       break;
125     default:
126       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
127       break;
128   }
129
130   GST_OBJECT_UNLOCK (self);
131 }
132
133 static gboolean
134 gst_video_codec_test_sink_start (GstBaseSink * sink)
135 {
136   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
137   GError *error = NULL;
138   GFile *file = NULL;
139   gboolean ret = TRUE;
140
141   GST_OBJECT_LOCK (self);
142
143   self->checksum = g_checksum_new (self->hash);
144   if (self->location)
145     file = g_file_new_for_path (self->location);
146
147   GST_OBJECT_UNLOCK (self);
148
149   if (file) {
150     self->ostream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
151             G_FILE_CREATE_REPLACE_DESTINATION, NULL, &error));
152     if (!self->ostream) {
153       GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
154           ("Failed to open '%s' for writing.", self->location),
155           ("Open failed failed: %s", error->message));
156       g_error_free (error);
157       ret = FALSE;
158     }
159
160     g_object_unref (file);
161   }
162
163   return ret;
164 }
165
166 static gboolean
167 gst_video_codec_test_sink_stop (GstBaseSink * sink)
168 {
169   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
170
171   g_checksum_free (self->checksum);
172   self->checksum = NULL;
173
174   if (self->ostream) {
175     GError *error = NULL;
176
177     if (!g_output_stream_close (self->ostream, NULL, &error)) {
178       GST_ELEMENT_WARNING (self, RESOURCE, CLOSE,
179           ("Did not close '%s' properly", self->location),
180           ("Failed to close stream: %s", error->message));
181     }
182
183     g_clear_object (&self->ostream);
184   }
185
186   return TRUE;
187 }
188
189 static GstFlowReturn
190 gst_video_codec_test_sink_process_data (GstVideoCodecTestSink * self,
191     const guchar * data, gssize length)
192 {
193   GError *error = NULL;
194
195   g_checksum_update (self->checksum, data, length);
196
197   if (!self->ostream)
198     return GST_FLOW_OK;
199
200   if (!g_output_stream_write_all (self->ostream, data, length, NULL, NULL,
201           &error)) {
202     GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
203         ("Failed to write video data into '%s'", self->location),
204         ("Writing %" G_GSIZE_FORMAT " bytes failed: %s", length,
205             error->message));
206     g_error_free (error);
207     return GST_FLOW_ERROR;
208   }
209
210   return GST_FLOW_OK;
211 }
212
213 static GstFlowReturn
214 gst_video_codec_test_sink_process_i42x (GstVideoCodecTestSink * self,
215     GstVideoFrame * frame)
216 {
217   guint plane;
218
219   for (plane = 0; plane < 3; plane++) {
220     gint y;
221     guint stride;
222     const guchar *data;
223
224     stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, plane);
225     data = GST_VIDEO_FRAME_PLANE_DATA (frame, plane);
226
227     for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, plane); y++) {
228       gsize length = GST_VIDEO_INFO_COMP_WIDTH (&self->vinfo, plane) *
229           GST_VIDEO_INFO_COMP_PSTRIDE (&self->vinfo, plane);
230       GstFlowReturn ret;
231
232       ret = gst_video_codec_test_sink_process_data (self, data, length);
233       if (ret != GST_FLOW_OK)
234         return ret;
235
236       data += stride;
237     }
238   }
239
240   return GST_FLOW_OK;
241 }
242
243 static GstFlowReturn
244 gst_video_codec_test_sink_process_nv12 (GstVideoCodecTestSink * self,
245     GstVideoFrame * frame)
246 {
247   gint x, y, comp;
248   guint stride;
249   const guchar *data;
250
251   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
252   data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
253
254   for (y = 0; y < GST_VIDEO_INFO_HEIGHT (&self->vinfo); y++) {
255     gsize length = GST_VIDEO_INFO_WIDTH (&self->vinfo);
256     GstFlowReturn ret;
257
258     ret = gst_video_codec_test_sink_process_data (self, data, length);
259     if (ret != GST_FLOW_OK)
260       return ret;
261
262     data += stride;
263   }
264
265   /* Deinterleave the UV plane */
266   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 1);
267
268   for (comp = 0; comp < 2; comp++) {
269     data = GST_VIDEO_FRAME_PLANE_DATA (frame, 1);
270
271     for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, 1); y++) {
272       guint width = GST_ROUND_UP_2 (GST_VIDEO_INFO_WIDTH (&self->vinfo)) / 2;
273
274       for (x = 0; x < width; x++) {
275         GstFlowReturn ret;
276
277         ret = gst_video_codec_test_sink_process_data (self,
278             data + 2 * x + comp, 1);
279
280         if (ret != GST_FLOW_OK)
281           return ret;
282       }
283
284       data += stride;
285     }
286   }
287
288   return GST_FLOW_OK;
289 }
290
291 static GstFlowReturn
292 gst_video_codec_test_sink_render (GstBaseSink * sink, GstBuffer * buffer)
293 {
294   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
295   GstVideoFrame frame;
296
297   if (!gst_video_frame_map (&frame, &self->vinfo, buffer, GST_MAP_READ))
298     return GST_FLOW_ERROR;
299
300   self->process (self, &frame);
301
302   gst_video_frame_unmap (&frame);
303   return GST_FLOW_OK;
304 }
305
306 static gboolean
307 gst_video_codec_test_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
308 {
309   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
310
311   if (!gst_video_info_from_caps (&self->vinfo, caps))
312     return FALSE;
313
314   switch (GST_VIDEO_INFO_FORMAT (&self->vinfo)) {
315     case GST_VIDEO_FORMAT_I420:
316     case GST_VIDEO_FORMAT_I420_10LE:
317     case GST_VIDEO_FORMAT_Y42B:
318     case GST_VIDEO_FORMAT_I422_10LE:
319     case GST_VIDEO_FORMAT_I420_12LE:
320     case GST_VIDEO_FORMAT_I422_12LE:
321     case GST_VIDEO_FORMAT_Y444:
322     case GST_VIDEO_FORMAT_Y444_10LE:
323     case GST_VIDEO_FORMAT_Y444_12LE:
324       self->process = gst_video_codec_test_sink_process_i42x;
325       break;
326     case GST_VIDEO_FORMAT_NV12:
327       self->process = gst_video_codec_test_sink_process_nv12;
328       break;
329     default:
330       g_assert_not_reached ();
331       break;
332   }
333
334   return TRUE;
335 }
336
337 static gboolean
338 gst_video_codec_test_sink_propose_allocation (GstBaseSink * sink,
339     GstQuery * query)
340 {
341   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
342   return TRUE;
343 }
344
345 static gboolean
346 gst_video_codec_test_sink_event (GstBaseSink * sink, GstEvent * event)
347 {
348   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
349
350   if (event->type == GST_EVENT_EOS) {
351     const gchar *checksum_type = "UNKNOWN";
352
353     switch (self->hash) {
354       case G_CHECKSUM_MD5:
355         checksum_type = "MD5";
356         break;
357       case G_CHECKSUM_SHA1:
358         checksum_type = "SHA1";
359         break;
360       case G_CHECKSUM_SHA256:
361         checksum_type = "SHA256";
362         break;
363       case G_CHECKSUM_SHA512:
364         checksum_type = "SHA512";
365         break;
366       case G_CHECKSUM_SHA384:
367         checksum_type = "SHA384";
368         break;
369       default:
370         g_assert_not_reached ();
371         break;
372     }
373
374     gst_element_post_message (GST_ELEMENT (self),
375         gst_message_new_element (GST_OBJECT (self),
376             gst_structure_new ("conformance/checksum", "checksum-type",
377                 G_TYPE_STRING, checksum_type, "checksum", G_TYPE_STRING,
378                 g_checksum_get_string (self->checksum), NULL)));
379     g_checksum_reset (self->checksum);
380   }
381
382   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
383 }
384
385 static void
386 gst_video_codec_test_sink_init (GstVideoCodecTestSink * sink)
387 {
388   gst_base_sink_set_sync (GST_BASE_SINK (sink), FALSE);
389   sink->hash = G_CHECKSUM_MD5;
390 }
391
392 static void
393 gst_video_codec_test_sink_finalize (GObject * object)
394 {
395   GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
396
397   g_free (self->location);
398
399   G_OBJECT_CLASS (parent_class)->finalize (object);
400 }
401
402 static void
403 gst_video_codec_test_sink_class_init (GstVideoCodecTestSinkClass * klass)
404 {
405   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
406   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
407   GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass);
408
409   gobject_class->set_property = gst_video_codec_test_sink_set_property;
410   gobject_class->get_property = gst_video_codec_test_sink_get_property;
411   gobject_class->finalize = gst_video_codec_test_sink_finalize;
412
413   base_sink_class->start = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_start);
414   base_sink_class->stop = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_stop);
415   base_sink_class->render =
416       GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_render);
417   base_sink_class->set_caps =
418       GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_set_caps);
419   base_sink_class->propose_allocation =
420       GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_propose_allocation);
421   base_sink_class->event = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_event);
422
423   gst_element_class_add_static_pad_template (element_class,
424       &gst_video_codec_test_sink_template);
425
426   g_object_class_install_property (gobject_class, PROP_LOCATION,
427       g_param_spec_string ("location", "Location",
428           "File path to store non-padded I420 stream (optional).", NULL,
429           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
430
431   gst_element_class_set_static_metadata (element_class,
432       "Video CODEC Test Sink", "Debug/video/Sink",
433       "Sink to test video CODEC conformance",
434       "Nicolas Dufresne <nicolas.dufresne@collabora.com");
435 }