1 /*
2 * GStreamer
3 * Copyright (C) 2006 Stefan Kost <ensonic@users.sf.net>
4 * Copyright (c) 2020 Anthony Violo <anthony.violo@ubicast.eu>
5 * Copyright (c) 2020 Thibault Saunier <tsaunier@igalia.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22
23 /**
24 * plugin-qroverlay
25 *
26 * Since: 1.20
27 */
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include <gst/gst.h>
34 #include <gst/base/gstbasetransform.h>
35 #include <json-glib/json-glib.h>
36
37 #include <qrencode.h>
38 #include <string.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41
42 #include "gstbaseqroverlay.h"
43
44 GST_DEBUG_CATEGORY_STATIC (gst_base_qr_overlay_debug);
45 #define GST_CAT_DEFAULT gst_base_qr_overlay_debug
46
47 enum
48 {
49 PROP_0,
50 PROP_X_AXIS,
51 PROP_Y_AXIS,
52 PROP_PIXEL_SIZE,
53 PROP_QRCODE_ERROR_CORRECTION,
54 };
55
56 typedef struct _GstBaseQROverlayPrivate GstBaseQROverlayPrivate;
57 struct _GstBaseQROverlayPrivate
58 {
59 gfloat qrcode_size;
60 guint qrcode_quality;
61 guint span_frame;
62 QRecLevel level;
63 gfloat x_percent;
64 gfloat y_percent;
65 GstElement *overlaycomposition;
66 GstVideoInfo info;
67 gboolean valid;
68
69 GstPad *sinkpad, *srcpad;
70 GstVideoOverlayComposition *prev_overlay;
71 };
72
73 #define PRIV(s) gst_base_qr_overlay_get_instance_private (GST_BASE_QR_OVERLAY (s))
74
75 #define OVERLAY_COMPOSITION_CAPS GST_VIDEO_CAPS_MAKE (GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS)
76
77 #define ALL_CAPS OVERLAY_COMPOSITION_CAPS ";" \
78 GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
79
80 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
81 GST_PAD_SINK,
82 GST_PAD_ALWAYS,
83 GST_STATIC_CAPS (ALL_CAPS)
84 );
85
86 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
87 GST_PAD_SRC,
88 GST_PAD_ALWAYS,
89 GST_STATIC_CAPS (ALL_CAPS)
90 );
91
92 #define DEFAULT_PROP_QUALITY 1
93 #define DEFAULT_PROP_PIXEL_SIZE 3
94
95 #define GST_TYPE_QRCODE_QUALITY (gst_qrcode_quality_get_type())
96 static GType
gst_qrcode_quality_get_type(void)97 gst_qrcode_quality_get_type (void)
98 {
99 static GType qrcode_quality_type = 0;
100
101 static const GEnumValue qrcode_quality[] = {
102 {0, "Level L", "Approx 7%"},
103 {1, "Level M", "Approx 15%"},
104 {2, "Level Q", "Approx 25%"},
105 {3, "Level H", "Approx 30%"},
106 {0, NULL, NULL},
107 };
108
109 if (!qrcode_quality_type) {
110 qrcode_quality_type =
111 g_enum_register_static ("GstQrcodeOverlayCorrection", qrcode_quality);
112 }
113 return qrcode_quality_type;
114 }
115
116 #define gst_base_qr_overlay_parent_class parent_class
117 G_DEFINE_TYPE_WITH_PRIVATE (GstBaseQROverlay, gst_base_qr_overlay,
118 GST_TYPE_BIN);
119
120 static void gst_base_qr_overlay_set_property (GObject * object, guint prop_id,
121 const GValue * value, GParamSpec * pspec);
122 static void gst_base_qr_overlay_get_property (GObject * object, guint prop_id,
123 GValue * value, GParamSpec * pspec);
124
125 static void
gst_base_qr_overlay_caps_changed_cb(GstBaseQROverlay * self,GstCaps * caps,gint window_width,gint window_height,GstElement * overlay)126 gst_base_qr_overlay_caps_changed_cb (GstBaseQROverlay * self, GstCaps * caps,
127 gint window_width, gint window_height, GstElement * overlay)
128 {
129 GstBaseQROverlayPrivate *priv = PRIV (self);
130
131 if (gst_video_info_from_caps (&priv->info, caps))
132 priv->valid = TRUE;
133 else
134 priv->valid = FALSE;
135 }
136
137 static GstVideoOverlayComposition *
draw_overlay(GstBaseQROverlay * self,QRcode * qrcode)138 draw_overlay (GstBaseQROverlay * self, QRcode * qrcode)
139 {
140 guint8 *qr_data, *pixels;
141 gint stride, pstride, y, x, yy, square_size;
142 gsize offset, line_offset;
143 GstVideoInfo info;
144 GstVideoOverlayRectangle *rect;
145 GstVideoOverlayComposition *comp;
146 GstBuffer *buf;
147 GstBaseQROverlayPrivate *priv = PRIV (self);
148
149 gst_video_info_init (&info);
150
151 square_size = (qrcode->width + 4 * 2) * priv->qrcode_size;
152 gst_video_info_set_format (&info, GST_VIDEO_FORMAT_ARGB, square_size,
153 square_size);
154
155 pixels = g_malloc ((size_t) info.size);
156 stride = info.stride[0];
157 pstride = info.finfo->pixel_stride[0];
158
159 /* White background */
160 for (y = 0; y < info.height; y++)
161 memset (&pixels[y * stride], 0xff, stride);
162
163 /* Draw the black QR code blocks with 4px white space around it
164 * on top */
165 line_offset = 4 * priv->qrcode_size * stride;
166 qr_data = qrcode->data;
167 for (y = 0; y < qrcode->width; y++) {
168 for (x = 0; x < (qrcode->width); x++) {
169 for (yy = 0; yy < priv->qrcode_size * pstride; yy += pstride) {
170 if (!(*qr_data & 1))
171 continue;
172
173 offset =
174 (((line_offset + (stride * (yy / pstride))) +
175 x * priv->qrcode_size * pstride)) +
176 (priv->qrcode_size * pstride) + (4 * priv->qrcode_size * pstride);
177
178 for (gint i = 0; i < priv->qrcode_size * pstride; i += pstride) {
179 pixels[offset + i] = 0x00;
180 pixels[offset + i + 1] = 0x00;
181 pixels[offset + i + 2] = 0x00;
182 }
183 }
184 qr_data++;
185 }
186 line_offset += (stride * priv->qrcode_size);
187 }
188
189 buf = gst_buffer_new_wrapped (pixels, info.size);
190 gst_buffer_add_video_meta (buf, GST_VIDEO_FRAME_FLAG_NONE,
191 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB, info.width, info.height);
192
193 x = (int) (priv->info.width - square_size) * (priv->x_percent / 100);
194 x = GST_ROUND_DOWN_2 (x);
195 y = (int) (priv->info.height - square_size) * (priv->y_percent / 100);
196 y = GST_ROUND_DOWN_4 (y);
197
198 rect = gst_video_overlay_rectangle_new_raw (buf, x, y,
199 info.width, info.height, GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE);
200 comp = gst_video_overlay_composition_new (rect);
201 gst_video_overlay_rectangle_unref (rect);
202
203 return comp;
204 }
205
206 static GstVideoOverlayComposition *
gst_base_qr_overlay_draw_cb(GstBaseQROverlay * self,GstSample * sample,GstElement * _)207 gst_base_qr_overlay_draw_cb (GstBaseQROverlay * self, GstSample * sample,
208 GstElement * _)
209 {
210 GstBaseQROverlayPrivate *priv = PRIV (self);
211 QRcode *qrcode;
212 gchar *content;
213 gboolean reuse_previous = FALSE;
214 GstVideoOverlayComposition *overlay = NULL;
215 GstBuffer *buffer = gst_sample_get_buffer (sample);
216 GstSegment *segment = gst_sample_get_segment (sample);
217 GstClockTime rtime = gst_segment_to_running_time (segment, GST_FORMAT_TIME,
218 GST_BUFFER_PTS (buffer));
219
220 if (!priv->valid) {
221 GST_ERROR_OBJECT (self, "Trying to draw before negotiation?");
222
223 return NULL;
224 }
225
226 if (GST_CLOCK_TIME_IS_VALID (rtime))
227 gst_object_sync_values (GST_OBJECT (self), rtime);
228
229 content =
230 GST_BASE_QR_OVERLAY_GET_CLASS (self)->get_content (GST_BASE_QR_OVERLAY
231 (self), buffer, &priv->info, &reuse_previous);
232 if (reuse_previous && priv->prev_overlay) {
233 overlay = gst_video_overlay_composition_ref (priv->prev_overlay);
234 } else if (content) {
235 GST_INFO_OBJECT (self, "String will be encoded : %s", content);
236 qrcode =
237 QRcode_encodeString (content, 0, priv->qrcode_quality, QR_MODE_8, 0);
238
239 if (qrcode) {
240 GST_DEBUG_OBJECT (self, "String encoded");
241 overlay = draw_overlay (GST_BASE_QR_OVERLAY (self), qrcode);
242 gst_mini_object_replace (((GstMiniObject **) & priv->prev_overlay),
243 (GstMiniObject *) overlay);
244 } else {
245 GST_WARNING_OBJECT (self, "Could not encode content: %s", content);
246 }
247 }
248 g_free (content);
249
250 return overlay;
251 }
252
253 /* GObject vmethod implementations */
254
255 static void
gst_base_qr_overlay_dispose(GObject * object)256 gst_base_qr_overlay_dispose (GObject * object)
257 {
258 GstBaseQROverlayPrivate *priv = PRIV (object);
259
260 gst_mini_object_replace (((GstMiniObject **) & priv->prev_overlay), NULL);
261 }
262
263 /* initialize the qroverlay's class */
264 static void
gst_base_qr_overlay_class_init(GstBaseQROverlayClass * klass)265 gst_base_qr_overlay_class_init (GstBaseQROverlayClass * klass)
266 {
267 GObjectClass *gobject_class;
268 GstElementClass *gstelement_class;
269
270 gobject_class = (GObjectClass *) klass;
271 gstelement_class = (GstElementClass *) klass;
272
273 gobject_class->set_property = gst_base_qr_overlay_set_property;
274 gobject_class->get_property = gst_base_qr_overlay_get_property;
275 gobject_class->dispose = gst_base_qr_overlay_dispose;
276
277 GST_DEBUG_CATEGORY_INIT (gst_base_qr_overlay_debug, "qroverlay", 0,
278 "Qrcode overlay base class");
279
280 g_object_class_install_property (gobject_class,
281 PROP_X_AXIS, g_param_spec_float ("x",
282 "X position (in percent of the width)",
283 "X position (in percent of the width)",
284 0.0, 100.0, 50.0, G_PARAM_READWRITE));
285
286 g_object_class_install_property (gobject_class,
287 PROP_Y_AXIS, g_param_spec_float ("y",
288 "Y position (in percent of the height)",
289 "Y position (in percent of the height)",
290 0.0, 100.0, 50.0, G_PARAM_READWRITE));
291
292 g_object_class_install_property (gobject_class,
293 PROP_PIXEL_SIZE, g_param_spec_float ("pixel-size",
294 "pixel-size", "Pixel size of each Qrcode pixel",
295 1, 100.0, DEFAULT_PROP_PIXEL_SIZE, G_PARAM_READWRITE));
296
297 g_object_class_install_property (gobject_class, PROP_QRCODE_ERROR_CORRECTION,
298 g_param_spec_enum ("qrcode-error-correction", "qrcode-error-correction",
299 "qrcode-error-correction", GST_TYPE_QRCODE_QUALITY,
300 DEFAULT_PROP_QUALITY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
301
302 gst_element_class_add_pad_template (gstelement_class,
303 gst_static_pad_template_get (&src_template));
304 gst_element_class_add_pad_template (gstelement_class,
305 gst_static_pad_template_get (&sink_template));
306
307 gst_type_mark_as_plugin_api (GST_TYPE_QRCODE_QUALITY, 0);
308 gst_type_mark_as_plugin_api (GST_TYPE_QRCODE_QUALITY, 0);
309 }
310
311 /* initialize the new element
312 * initialize instance structure
313 */
314 static void
gst_base_qr_overlay_init(GstBaseQROverlay * self)315 gst_base_qr_overlay_init (GstBaseQROverlay * self)
316 {
317 GstBaseQROverlayPrivate *priv = PRIV (self);
318
319 priv->x_percent = 50.0;
320 priv->y_percent = 50.0;
321 priv->qrcode_quality = DEFAULT_PROP_QUALITY;
322 priv->span_frame = 0;
323 priv->qrcode_size = DEFAULT_PROP_PIXEL_SIZE;
324 priv->overlaycomposition =
325 gst_element_factory_make ("overlaycomposition", NULL);
326 gst_video_info_init (&priv->info);
327
328 if (priv->overlaycomposition) {
329 GstPadTemplate *sink_tmpl = gst_static_pad_template_get (&sink_template);
330 GstPadTemplate *src_tmpl = gst_static_pad_template_get (&src_template);
331
332 gst_bin_add (GST_BIN (self), priv->overlaycomposition);
333
334 gst_element_add_pad (GST_ELEMENT_CAST (self),
335 gst_ghost_pad_new_from_template ("sink",
336 priv->overlaycomposition->sinkpads->data, sink_tmpl));
337 gst_element_add_pad (GST_ELEMENT_CAST (self),
338 gst_ghost_pad_new_from_template ("src",
339 priv->overlaycomposition->srcpads->data, src_tmpl));
340 gst_object_unref (sink_tmpl);
341 gst_object_unref (src_tmpl);
342
343 g_signal_connect_swapped (priv->overlaycomposition, "draw",
344 G_CALLBACK (gst_base_qr_overlay_draw_cb), self);
345 g_signal_connect_swapped (priv->overlaycomposition, "caps-changed",
346 G_CALLBACK (gst_base_qr_overlay_caps_changed_cb), self);
347 }
348 }
349
350 static void
gst_base_qr_overlay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)351 gst_base_qr_overlay_set_property (GObject * object, guint prop_id,
352 const GValue * value, GParamSpec * pspec)
353 {
354 GstBaseQROverlayPrivate *priv = PRIV (object);
355
356 switch (prop_id) {
357 case PROP_X_AXIS:
358 priv->x_percent = g_value_get_float (value);
359 break;
360 case PROP_Y_AXIS:
361 priv->y_percent = g_value_get_float (value);
362 break;
363 case PROP_PIXEL_SIZE:
364 priv->qrcode_size = g_value_get_float (value);
365 break;
366 case PROP_QRCODE_ERROR_CORRECTION:
367 priv->qrcode_quality = g_value_get_enum (value);
368 break;
369 default:
370 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
371 break;
372 }
373 }
374
375 static void
gst_base_qr_overlay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)376 gst_base_qr_overlay_get_property (GObject * object, guint prop_id,
377 GValue * value, GParamSpec * pspec)
378 {
379 GstBaseQROverlayPrivate *priv = PRIV (object);
380
381 switch (prop_id) {
382 case PROP_X_AXIS:
383 g_value_set_float (value, priv->x_percent);
384 break;
385 case PROP_Y_AXIS:
386 g_value_set_float (value, priv->y_percent);
387 break;
388 case PROP_PIXEL_SIZE:
389 g_value_set_float (value, priv->qrcode_size);
390 break;
391 case PROP_QRCODE_ERROR_CORRECTION:
392 g_value_set_enum (value, priv->qrcode_quality);
393 break;
394 default:
395 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
396 break;
397 }
398 }
399