• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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