1 /* GStreamer
2  * Copyright (C) <2007> Wim Taymans <wim@fluendo.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
17  * Boston, MA 02110-1335, USA.
18  */
19 /**
20  * SECTION:element-simplevideomark
21  * @title: simplevideomark
22  * @see_also: #GstVideoDetect
23  *
24  * This plugin produces #GstSimpleVideoMark::pattern-count squares in the bottom left
25  * corner of the video frames. The squares have a width and height of
26  * respectively #GstSimpleVideoMark:pattern-width and #GstSimpleVideoMark:pattern-height.
27  * Even squares will be black and odd squares will be white.
28  *
29  * After writing the pattern, #GstSimpleVideoMark:pattern-data-count squares after the
30  * pattern squares are produced as the bitarray given in
31  * #GstSimpleVideoMark:pattern-data. 1 bits will produce white squares and 0 bits will
32  * produce black squares.
33  *
34  * The element can be enabled with the #GstSimpleVideoMark:enabled property. It is
35  * mostly used together with the #GstVideoDetect plugin.
36  *
37  * ## Example launch line
38  * |[
39  * gst-launch-1.0 videotestsrc ! simplevideomark ! videoconvert ! ximagesink
40  * ]| Add the default black/white squares at the bottom left of the video frames.
41  *
42  */
43 
44 #ifdef HAVE_CONFIG_H
45 #include "config.h"
46 #endif
47 
48 #include <gst/gst.h>
49 #include <gst/video/video.h>
50 #include <gst/video/gstvideofilter.h>
51 #include "gstsimplevideomark.h"
52 
53 GST_DEBUG_CATEGORY_STATIC (gst_video_mark_debug_category);
54 #define GST_CAT_DEFAULT gst_video_mark_debug_category
55 
56 /* prototypes */
57 
58 
59 static void gst_video_mark_set_property (GObject * object,
60     guint property_id, const GValue * value, GParamSpec * pspec);
61 static void gst_video_mark_get_property (GObject * object,
62     guint property_id, GValue * value, GParamSpec * pspec);
63 static void gst_video_mark_dispose (GObject * object);
64 static void gst_video_mark_finalize (GObject * object);
65 
66 static gboolean gst_video_mark_start (GstBaseTransform * trans);
67 static gboolean gst_video_mark_stop (GstBaseTransform * trans);
68 static gboolean gst_video_mark_set_info (GstVideoFilter * filter,
69     GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps,
70     GstVideoInfo * out_info);
71 static GstFlowReturn gst_video_mark_transform_frame_ip (GstVideoFilter * filter,
72     GstVideoFrame * frame);
73 
74 enum
75 {
76   PROP_0,
77   PROP_PATTERN_WIDTH,
78   PROP_PATTERN_HEIGHT,
79   PROP_PATTERN_COUNT,
80   PROP_PATTERN_DATA_COUNT,
81   PROP_PATTERN_DATA,
82   PROP_ENABLED,
83   PROP_LEFT_OFFSET,
84   PROP_BOTTOM_OFFSET
85 };
86 
87 #define DEFAULT_PATTERN_WIDTH        4
88 #define DEFAULT_PATTERN_HEIGHT       16
89 #define DEFAULT_PATTERN_COUNT        4
90 #define DEFAULT_PATTERN_DATA_COUNT   5
91 #define DEFAULT_PATTERN_DATA         10
92 #define DEFAULT_ENABLED              TRUE
93 #define DEFAULT_LEFT_OFFSET          0
94 #define DEFAULT_BOTTOM_OFFSET        0
95 
96 /* pad templates */
97 
98 #define VIDEO_CAPS \
99     GST_VIDEO_CAPS_MAKE( \
100         "{ I420, YV12, Y41B, Y42B, Y444, YUY2, UYVY, AYUV, YVYU }")
101 
102 
103 /* class initialization */
104 
105 G_DEFINE_TYPE_WITH_CODE (GstSimpleVideoMark, gst_video_mark,
106     GST_TYPE_VIDEO_FILTER,
107     GST_DEBUG_CATEGORY_INIT (gst_video_mark_debug_category, "simplevideomark",
108         0, "debug category for simplevideomark element"));
109 
110 static void
gst_video_mark_class_init(GstSimpleVideoMarkClass * klass)111 gst_video_mark_class_init (GstSimpleVideoMarkClass * klass)
112 {
113   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
114   GstBaseTransformClass *base_transform_class =
115       GST_BASE_TRANSFORM_CLASS (klass);
116   GstVideoFilterClass *video_filter_class = GST_VIDEO_FILTER_CLASS (klass);
117 
118   gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass),
119       gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
120           gst_caps_from_string (VIDEO_CAPS)));
121   gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass),
122       gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
123           gst_caps_from_string (VIDEO_CAPS)));
124 
125   gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass),
126       "Video marker", "Filter/Effect/Video",
127       "Marks a video signal with a pattern", "Wim Taymans <wim@fluendo.com>");
128 
129   gobject_class->set_property = gst_video_mark_set_property;
130   gobject_class->get_property = gst_video_mark_get_property;
131   gobject_class->dispose = gst_video_mark_dispose;
132   gobject_class->finalize = gst_video_mark_finalize;
133   base_transform_class->start = GST_DEBUG_FUNCPTR (gst_video_mark_start);
134   base_transform_class->stop = GST_DEBUG_FUNCPTR (gst_video_mark_stop);
135   video_filter_class->set_info = GST_DEBUG_FUNCPTR (gst_video_mark_set_info);
136   video_filter_class->transform_frame_ip =
137       GST_DEBUG_FUNCPTR (gst_video_mark_transform_frame_ip);
138 
139   g_object_class_install_property (gobject_class, PROP_PATTERN_WIDTH,
140       g_param_spec_int ("pattern-width", "Pattern width",
141           "The width of the pattern markers", 1, G_MAXINT,
142           DEFAULT_PATTERN_WIDTH,
143           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
144   g_object_class_install_property (gobject_class, PROP_PATTERN_HEIGHT,
145       g_param_spec_int ("pattern-height", "Pattern height",
146           "The height of the pattern markers", 1, G_MAXINT,
147           DEFAULT_PATTERN_HEIGHT,
148           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
149   g_object_class_install_property (gobject_class, PROP_PATTERN_COUNT,
150       g_param_spec_int ("pattern-count", "Pattern count",
151           "The number of pattern markers", 0, G_MAXINT,
152           DEFAULT_PATTERN_COUNT,
153           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
154   g_object_class_install_property (gobject_class, PROP_PATTERN_DATA_COUNT,
155       g_param_spec_int ("pattern-data-count", "Pattern data count",
156           "The number of extra data pattern markers", 0, 64,
157           DEFAULT_PATTERN_DATA_COUNT,
158           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
159   g_object_class_install_property (gobject_class, PROP_PATTERN_DATA,
160       g_param_spec_uint64 ("pattern-data", "Pattern data",
161           "The extra data pattern markers", 0, G_MAXUINT64,
162           DEFAULT_PATTERN_DATA,
163           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
164   g_object_class_install_property (gobject_class, PROP_ENABLED,
165       g_param_spec_boolean ("enabled", "Enabled",
166           "Enable or disable the filter",
167           DEFAULT_ENABLED,
168           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
169   g_object_class_install_property (gobject_class, PROP_LEFT_OFFSET,
170       g_param_spec_int ("left-offset", "Left Offset",
171           "The offset from the left border where the pattern starts", 0,
172           G_MAXINT, DEFAULT_LEFT_OFFSET,
173           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
174   g_object_class_install_property (gobject_class, PROP_BOTTOM_OFFSET,
175       g_param_spec_int ("bottom-offset", "Bottom Offset",
176           "The offset from the bottom border where the pattern starts", 0,
177           G_MAXINT, DEFAULT_BOTTOM_OFFSET,
178           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
179 
180 }
181 
182 static void
gst_video_mark_init(GstSimpleVideoMark * simplevideomark)183 gst_video_mark_init (GstSimpleVideoMark * simplevideomark)
184 {
185 }
186 
187 void
gst_video_mark_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)188 gst_video_mark_set_property (GObject * object, guint property_id,
189     const GValue * value, GParamSpec * pspec)
190 {
191   GstSimpleVideoMark *simplevideomark = GST_SIMPLE_VIDEO_MARK (object);
192 
193   GST_DEBUG_OBJECT (simplevideomark, "set_property");
194 
195   switch (property_id) {
196     case PROP_PATTERN_WIDTH:
197       simplevideomark->pattern_width = g_value_get_int (value);
198       break;
199     case PROP_PATTERN_HEIGHT:
200       simplevideomark->pattern_height = g_value_get_int (value);
201       break;
202     case PROP_PATTERN_COUNT:
203       simplevideomark->pattern_count = g_value_get_int (value);
204       break;
205     case PROP_PATTERN_DATA_COUNT:
206       simplevideomark->pattern_data_count = g_value_get_int (value);
207       break;
208     case PROP_PATTERN_DATA:
209       simplevideomark->pattern_data = g_value_get_uint64 (value);
210       break;
211     case PROP_ENABLED:
212       simplevideomark->enabled = g_value_get_boolean (value);
213       break;
214     case PROP_LEFT_OFFSET:
215       simplevideomark->left_offset = g_value_get_int (value);
216       break;
217     case PROP_BOTTOM_OFFSET:
218       simplevideomark->bottom_offset = g_value_get_int (value);
219       break;
220     default:
221       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
222       break;
223   }
224 }
225 
226 void
gst_video_mark_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)227 gst_video_mark_get_property (GObject * object, guint property_id,
228     GValue * value, GParamSpec * pspec)
229 {
230   GstSimpleVideoMark *simplevideomark = GST_SIMPLE_VIDEO_MARK (object);
231 
232   GST_DEBUG_OBJECT (simplevideomark, "get_property");
233 
234   switch (property_id) {
235     case PROP_PATTERN_WIDTH:
236       g_value_set_int (value, simplevideomark->pattern_width);
237       break;
238     case PROP_PATTERN_HEIGHT:
239       g_value_set_int (value, simplevideomark->pattern_height);
240       break;
241     case PROP_PATTERN_COUNT:
242       g_value_set_int (value, simplevideomark->pattern_count);
243       break;
244     case PROP_PATTERN_DATA_COUNT:
245       g_value_set_int (value, simplevideomark->pattern_data_count);
246       break;
247     case PROP_PATTERN_DATA:
248       g_value_set_uint64 (value, simplevideomark->pattern_data);
249       break;
250     case PROP_ENABLED:
251       g_value_set_boolean (value, simplevideomark->enabled);
252       break;
253     case PROP_LEFT_OFFSET:
254       g_value_set_int (value, simplevideomark->left_offset);
255       break;
256     case PROP_BOTTOM_OFFSET:
257       g_value_set_int (value, simplevideomark->bottom_offset);
258       break;
259     default:
260       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
261       break;
262   }
263 }
264 
265 void
gst_video_mark_dispose(GObject * object)266 gst_video_mark_dispose (GObject * object)
267 {
268   GstSimpleVideoMark *simplevideomark = GST_SIMPLE_VIDEO_MARK (object);
269 
270   GST_DEBUG_OBJECT (simplevideomark, "dispose");
271 
272   /* clean up as possible.  may be called multiple times */
273 
274   G_OBJECT_CLASS (gst_video_mark_parent_class)->dispose (object);
275 }
276 
277 void
gst_video_mark_finalize(GObject * object)278 gst_video_mark_finalize (GObject * object)
279 {
280   GstSimpleVideoMark *simplevideomark = GST_SIMPLE_VIDEO_MARK (object);
281 
282   GST_DEBUG_OBJECT (simplevideomark, "finalize");
283 
284   /* clean up object here */
285 
286   G_OBJECT_CLASS (gst_video_mark_parent_class)->finalize (object);
287 }
288 
289 static gboolean
gst_video_mark_start(GstBaseTransform * trans)290 gst_video_mark_start (GstBaseTransform * trans)
291 {
292   GstSimpleVideoMark *simplevideomark = GST_SIMPLE_VIDEO_MARK (trans);
293 
294   GST_DEBUG_OBJECT (simplevideomark, "start");
295 
296   return TRUE;
297 }
298 
299 static gboolean
gst_video_mark_stop(GstBaseTransform * trans)300 gst_video_mark_stop (GstBaseTransform * trans)
301 {
302   GstSimpleVideoMark *simplevideomark = GST_SIMPLE_VIDEO_MARK (trans);
303 
304   GST_DEBUG_OBJECT (simplevideomark, "stop");
305 
306   return TRUE;
307 }
308 
309 static gboolean
gst_video_mark_set_info(GstVideoFilter * filter,GstCaps * incaps,GstVideoInfo * in_info,GstCaps * outcaps,GstVideoInfo * out_info)310 gst_video_mark_set_info (GstVideoFilter * filter, GstCaps * incaps,
311     GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
312 {
313   GstSimpleVideoMark *simplevideomark = GST_SIMPLE_VIDEO_MARK (filter);
314 
315   GST_DEBUG_OBJECT (simplevideomark, "set_info");
316 
317   return TRUE;
318 }
319 
320 static void
gst_video_mark_draw_box(GstSimpleVideoMark * simplevideomark,guint8 * data,gint width,gint height,gint row_stride,gint pixel_stride,guint8 color)321 gst_video_mark_draw_box (GstSimpleVideoMark * simplevideomark, guint8 * data,
322     gint width, gint height, gint row_stride, gint pixel_stride, guint8 color)
323 {
324   gint i, j;
325 
326   for (i = 0; i < height; i++) {
327     for (j = 0; j < width; j++) {
328       data[pixel_stride * j] = color;
329     }
330     data += row_stride;
331   }
332 }
333 
334 static gint
calculate_pw(gint pw,gint x,gint width)335 calculate_pw (gint pw, gint x, gint width)
336 {
337   if (x < 0)
338     pw += x;
339   else if ((x + pw) > width)
340     pw = width - x;
341 
342   return pw;
343 }
344 
345 static GstFlowReturn
gst_video_mark_yuv(GstSimpleVideoMark * simplevideomark,GstVideoFrame * frame)346 gst_video_mark_yuv (GstSimpleVideoMark * simplevideomark, GstVideoFrame * frame)
347 {
348   gint i, pw, ph, row_stride, pixel_stride;
349   gint width, height, offset_calc, x, y;
350   guint8 *d;
351   guint64 pattern_shift;
352   guint8 color;
353   gint total_pattern;
354 
355   width = frame->info.width;
356   height = frame->info.height;
357 
358   pw = simplevideomark->pattern_width;
359   ph = simplevideomark->pattern_height;
360   row_stride = GST_VIDEO_FRAME_COMP_STRIDE (frame, 0);
361   pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (frame, 0);
362 
363   d = GST_VIDEO_FRAME_COMP_DATA (frame, 0);
364   offset_calc =
365       row_stride * (height - ph - simplevideomark->bottom_offset) +
366       pixel_stride * simplevideomark->left_offset;
367   x = simplevideomark->left_offset;
368   y = height - ph - simplevideomark->bottom_offset;
369 
370   total_pattern =
371       simplevideomark->pattern_count + simplevideomark->pattern_data_count;
372   /* If x and y offset values are outside the video, no need to draw */
373   if ((x + (pw * total_pattern)) < 0 || x > width || (y + height) < 0
374       || y > height) {
375     GST_ERROR_OBJECT (simplevideomark,
376         "simplevideomark pattern is outside the video. Not drawing.");
377     return GST_FLOW_OK;
378   }
379 
380   /* Offset calculation less than 0, then reset to 0 */
381   if (offset_calc < 0)
382     offset_calc = 0;
383   /* Y position of mark is negative or pattern exceeds the video height,
384      then recalculate pattern height for partial display */
385   if (y < 0)
386     ph += y;
387   else if ((y + ph) > height)
388     ph = height - y;
389   /* If pattern height is less than 0, need not draw anything */
390   if (ph < 0)
391     return GST_FLOW_OK;
392 
393   /* move to start of bottom left */
394   d += offset_calc;
395 
396   /* draw the bottom left pixels */
397   for (i = 0; i < simplevideomark->pattern_count; i++) {
398     gint draw_pw;
399 
400     if (i & 1)
401       /* odd pixels must be white */
402       color = 255;
403     else
404       color = 0;
405 
406     /* X position of mark is negative or pattern exceeds the video width,
407        then recalculate pattern width for partial display */
408     draw_pw = calculate_pw (pw, x, width);
409     /* If pattern width is less than 0, continue with the next pattern */
410     if (draw_pw < 0)
411       continue;
412 
413     /* draw box of width * height */
414     gst_video_mark_draw_box (simplevideomark, d, draw_pw, ph, row_stride,
415         pixel_stride, color);
416 
417     /* move to i-th pattern */
418     d += pixel_stride * draw_pw;
419     x += draw_pw;
420 
421     if ((x + (pw * (total_pattern - i - 1))) < 0 || x >= width)
422       return GST_FLOW_OK;
423   }
424 
425   pattern_shift =
426       G_GUINT64_CONSTANT (1) << (simplevideomark->pattern_data_count - 1);
427 
428   /* get the data of the pattern */
429   for (i = 0; i < simplevideomark->pattern_data_count; i++) {
430     gint draw_pw;
431     if (simplevideomark->pattern_data & pattern_shift)
432       color = 255;
433     else
434       color = 0;
435 
436     /* X position of mark is negative or pattern exceeds the video width,
437        then recalculate pattern width for partial display */
438     draw_pw = calculate_pw (pw, x, width);
439     /* If pattern width is less than 0, continue with the next pattern */
440     if (draw_pw < 0)
441       continue;
442 
443     gst_video_mark_draw_box (simplevideomark, d, draw_pw, ph, row_stride,
444         pixel_stride, color);
445 
446     pattern_shift >>= 1;
447 
448     /* move to i-th pattern data */
449     d += pixel_stride * draw_pw;
450     x += draw_pw;
451 
452     if ((x + (pw * (simplevideomark->pattern_data_count - i - 1))) < 0
453         || x >= width)
454       return GST_FLOW_OK;
455   }
456 
457   return GST_FLOW_OK;
458 }
459 
460 
461 static GstFlowReturn
gst_video_mark_transform_frame_ip(GstVideoFilter * filter,GstVideoFrame * frame)462 gst_video_mark_transform_frame_ip (GstVideoFilter * filter,
463     GstVideoFrame * frame)
464 {
465   GstSimpleVideoMark *simplevideomark = GST_SIMPLE_VIDEO_MARK (filter);
466 
467   GST_DEBUG_OBJECT (simplevideomark, "transform_frame_ip");
468 
469   if (simplevideomark->enabled)
470     return gst_video_mark_yuv (simplevideomark, frame);
471 
472   return GST_FLOW_OK;
473 }
474