1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2003> David Schleef <ds@schleef.org>
4  * Copyright (C) <2006> Julien Moutte <julien@moutte.net>
5  * Copyright (C) <2006> Zeeshan Ali <zeeshan.ali@nokia.com>
6  * Copyright (C) <2006-2008> Tim-Philipp Müller <tim centricular net>
7  * Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28 
29 #include <gst/video/video.h>
30 #include <gst/video/gstvideometa.h>
31 
32 #include "gstbasetextoverlay.h"
33 #include "gsttextoverlay.h"
34 #include "gsttimeoverlay.h"
35 #include "gstclockoverlay.h"
36 #include "gsttextrender.h"
37 #include <string.h>
38 #include <math.h>
39 
40 /* FIXME:
41  *  - use proper strides and offset for I420
42  *  - if text is wider than the video picture, it does not get
43  *    clipped properly during blitting (if wrapping is disabled)
44  */
45 
46 GST_DEBUG_CATEGORY (pango_debug);
47 #define GST_CAT_DEFAULT pango_debug
48 
49 #define DEFAULT_PROP_TEXT 	""
50 #define DEFAULT_PROP_SHADING	FALSE
51 #define DEFAULT_PROP_VALIGNMENT	GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
52 #define DEFAULT_PROP_HALIGNMENT	GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
53 #define DEFAULT_PROP_XPAD	25
54 #define DEFAULT_PROP_YPAD	25
55 #define DEFAULT_PROP_DELTAX	0
56 #define DEFAULT_PROP_DELTAY	0
57 #define DEFAULT_PROP_XPOS       0.5
58 #define DEFAULT_PROP_YPOS       0.5
59 #define DEFAULT_PROP_WRAP_MODE  GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
60 #define DEFAULT_PROP_FONT_DESC	""
61 #define DEFAULT_PROP_SILENT	FALSE
62 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
63 #define DEFAULT_PROP_WAIT_TEXT	TRUE
64 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
65 #define DEFAULT_PROP_VERTICAL_RENDER  FALSE
66 #define DEFAULT_PROP_SCALE_MODE GST_BASE_TEXT_OVERLAY_SCALE_MODE_NONE
67 #define DEFAULT_PROP_SCALE_PAR_N 1
68 #define DEFAULT_PROP_SCALE_PAR_D 1
69 #define DEFAULT_PROP_DRAW_SHADOW TRUE
70 #define DEFAULT_PROP_DRAW_OUTLINE TRUE
71 #define DEFAULT_PROP_COLOR      0xffffffff
72 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
73 #define DEFAULT_PROP_SHADING_VALUE    80
74 #define DEFAULT_PROP_TEXT_X 0
75 #define DEFAULT_PROP_TEXT_Y 0
76 #define DEFAULT_PROP_TEXT_WIDTH 1
77 #define DEFAULT_PROP_TEXT_HEIGHT 1
78 
79 #define MINIMUM_OUTLINE_OFFSET 1.0
80 #define DEFAULT_SCALE_BASIS    640
81 
82 enum
83 {
84   PROP_0,
85   PROP_TEXT,
86   PROP_SHADING,
87   PROP_SHADING_VALUE,
88   PROP_HALIGNMENT,
89   PROP_VALIGNMENT,
90   PROP_XPAD,
91   PROP_YPAD,
92   PROP_DELTAX,
93   PROP_DELTAY,
94   PROP_XPOS,
95   PROP_YPOS,
96   PROP_X_ABSOLUTE,
97   PROP_Y_ABSOLUTE,
98   PROP_WRAP_MODE,
99   PROP_FONT_DESC,
100   PROP_SILENT,
101   PROP_LINE_ALIGNMENT,
102   PROP_WAIT_TEXT,
103   PROP_AUTO_ADJUST_SIZE,
104   PROP_VERTICAL_RENDER,
105   PROP_SCALE_MODE,
106   PROP_SCALE_PAR,
107   PROP_COLOR,
108   PROP_DRAW_SHADOW,
109   PROP_DRAW_OUTLINE,
110   PROP_OUTLINE_COLOR,
111   PROP_TEXT_X,
112   PROP_TEXT_Y,
113   PROP_TEXT_WIDTH,
114   PROP_TEXT_HEIGHT,
115   PROP_LAST
116 };
117 
118 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
119 
120 #define BASE_TEXT_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
121 
122 #define BASE_TEXT_OVERLAY_ALL_CAPS BASE_TEXT_OVERLAY_CAPS ";" \
123     GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
124 
125 static GstStaticCaps sw_template_caps =
126 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
127 
128 static GstStaticPadTemplate src_template_factory =
129 GST_STATIC_PAD_TEMPLATE ("src",
130     GST_PAD_SRC,
131     GST_PAD_ALWAYS,
132     GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
133     );
134 
135 static GstStaticPadTemplate video_sink_template_factory =
136 GST_STATIC_PAD_TEMPLATE ("video_sink",
137     GST_PAD_SINK,
138     GST_PAD_ALWAYS,
139     GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
140     );
141 
142 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
143 static GType
gst_base_text_overlay_valign_get_type(void)144 gst_base_text_overlay_valign_get_type (void)
145 {
146   static GType base_text_overlay_valign_type = 0;
147   static const GEnumValue base_text_overlay_valign[] = {
148     {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
149     {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
150     {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
151     {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position",
152         "Absolute position clamped to canvas"},
153     {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
154     {GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE, "absolute", "Absolute position"},
155     {0, NULL, NULL},
156   };
157 
158   if (!base_text_overlay_valign_type) {
159     base_text_overlay_valign_type =
160         g_enum_register_static ("GstBaseTextOverlayVAlign",
161         base_text_overlay_valign);
162   }
163   return base_text_overlay_valign_type;
164 }
165 
166 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
167 static GType
gst_base_text_overlay_halign_get_type(void)168 gst_base_text_overlay_halign_get_type (void)
169 {
170   static GType base_text_overlay_halign_type = 0;
171   static const GEnumValue base_text_overlay_halign[] = {
172     {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
173     {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
174     {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
175     {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position",
176         "Absolute position clamped to canvas"},
177     {GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE, "absolute", "Absolute position"},
178     {0, NULL, NULL},
179   };
180 
181   if (!base_text_overlay_halign_type) {
182     base_text_overlay_halign_type =
183         g_enum_register_static ("GstBaseTextOverlayHAlign",
184         base_text_overlay_halign);
185   }
186   return base_text_overlay_halign_type;
187 }
188 
189 
190 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
191 static GType
gst_base_text_overlay_wrap_mode_get_type(void)192 gst_base_text_overlay_wrap_mode_get_type (void)
193 {
194   static GType base_text_overlay_wrap_mode_type = 0;
195   static const GEnumValue base_text_overlay_wrap_mode[] = {
196     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
197     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
198     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
199     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
200     {0, NULL, NULL},
201   };
202 
203   if (!base_text_overlay_wrap_mode_type) {
204     base_text_overlay_wrap_mode_type =
205         g_enum_register_static ("GstBaseTextOverlayWrapMode",
206         base_text_overlay_wrap_mode);
207   }
208   return base_text_overlay_wrap_mode_type;
209 }
210 
211 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
212 static GType
gst_base_text_overlay_line_align_get_type(void)213 gst_base_text_overlay_line_align_get_type (void)
214 {
215   static GType base_text_overlay_line_align_type = 0;
216   static const GEnumValue base_text_overlay_line_align[] = {
217     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
218     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
219     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
220     {0, NULL, NULL}
221   };
222 
223   if (!base_text_overlay_line_align_type) {
224     base_text_overlay_line_align_type =
225         g_enum_register_static ("GstBaseTextOverlayLineAlign",
226         base_text_overlay_line_align);
227   }
228   return base_text_overlay_line_align_type;
229 }
230 
231 #define GST_TYPE_BASE_TEXT_OVERLAY_SCALE_MODE (gst_base_text_overlay_scale_mode_get_type())
232 static GType
gst_base_text_overlay_scale_mode_get_type(void)233 gst_base_text_overlay_scale_mode_get_type (void)
234 {
235   static GType base_text_overlay_scale_mode_type = 0;
236   static const GEnumValue base_text_overlay_scale_mode[] = {
237     {GST_BASE_TEXT_OVERLAY_SCALE_MODE_NONE, "none", "none"},
238     {GST_BASE_TEXT_OVERLAY_SCALE_MODE_PAR, "par", "par"},
239     {GST_BASE_TEXT_OVERLAY_SCALE_MODE_DISPLAY, "display", "display"},
240     {GST_BASE_TEXT_OVERLAY_SCALE_MODE_USER, "user", "user"},
241     {0, NULL, NULL}
242   };
243 
244   if (!base_text_overlay_scale_mode_type) {
245     base_text_overlay_scale_mode_type =
246         g_enum_register_static ("GstBaseTextOverlayScaleMode",
247         base_text_overlay_scale_mode);
248   }
249   return base_text_overlay_scale_mode_type;
250 }
251 
252 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
253 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
254 #define GST_BASE_TEXT_OVERLAY_LOCK(ov)     (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
255 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov)   (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
256 #define GST_BASE_TEXT_OVERLAY_WAIT(ov)     (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
257 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov)   (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
258 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
259 
260 static GstElementClass *parent_class = NULL;
261 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
262 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
263     GstBaseTextOverlayClass * klass);
264 
265 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
266     element, GstStateChange transition);
267 
268 static GstCaps *gst_base_text_overlay_get_videosink_caps (GstPad * pad,
269     GstBaseTextOverlay * overlay, GstCaps * filter);
270 static GstCaps *gst_base_text_overlay_get_src_caps (GstPad * pad,
271     GstBaseTextOverlay * overlay, GstCaps * filter);
272 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
273     GstCaps * caps);
274 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
275     GstCaps * caps);
276 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
277     GstObject * parent, GstEvent * event);
278 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
279     GstObject * parent, GstQuery * query);
280 
281 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
282     GstObject * parent, GstEvent * event);
283 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
284     GstObject * parent, GstQuery * query);
285 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
286     GstObject * parent, GstBuffer * buffer);
287 
288 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
289     GstObject * parent, GstEvent * event);
290 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
291     GstObject * parent, GstBuffer * buffer);
292 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
293     GstObject * parent, GstPad * peer);
294 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
295     GstObject * parent);
296 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
297 
298 static void gst_base_text_overlay_finalize (GObject * object);
299 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
300     const GValue * value, GParamSpec * pspec);
301 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
302     GValue * value, GParamSpec * pspec);
303 
304 static void
305 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
306     PangoFontDescription * desc);
307 static gboolean gst_base_text_overlay_can_handle_caps (GstCaps * incaps);
308 
309 static void
310 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay);
311 
312 GType
gst_base_text_overlay_get_type(void)313 gst_base_text_overlay_get_type (void)
314 {
315   static GType type = 0;
316 
317   if (g_once_init_enter ((gsize *) & type)) {
318     static const GTypeInfo info = {
319       sizeof (GstBaseTextOverlayClass),
320       (GBaseInitFunc) NULL,
321       NULL,
322       (GClassInitFunc) gst_base_text_overlay_class_init,
323       NULL,
324       NULL,
325       sizeof (GstBaseTextOverlay),
326       0,
327       (GInstanceInitFunc) gst_base_text_overlay_init,
328     };
329 
330     g_once_init_leave ((gsize *) & type,
331         g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
332             0));
333   }
334 
335   return type;
336 }
337 
338 static gchar *
gst_base_text_overlay_get_text(GstBaseTextOverlay * overlay,GstBuffer * video_frame)339 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
340     GstBuffer * video_frame)
341 {
342   return g_strdup (overlay->default_text);
343 }
344 
345 static void
gst_base_text_overlay_class_init(GstBaseTextOverlayClass * klass)346 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
347 {
348   GObjectClass *gobject_class;
349   GstElementClass *gstelement_class;
350 
351   gobject_class = (GObjectClass *) klass;
352   gstelement_class = (GstElementClass *) klass;
353 
354   parent_class = g_type_class_peek_parent (klass);
355 
356   gobject_class->finalize = gst_base_text_overlay_finalize;
357   gobject_class->set_property = gst_base_text_overlay_set_property;
358   gobject_class->get_property = gst_base_text_overlay_get_property;
359 
360   gst_element_class_add_static_pad_template (gstelement_class,
361       &src_template_factory);
362   gst_element_class_add_static_pad_template (gstelement_class,
363       &video_sink_template_factory);
364 
365   gstelement_class->change_state =
366       GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
367 
368   klass->get_text = gst_base_text_overlay_get_text;
369 
370   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
371       g_param_spec_string ("text", "text",
372           "Text to be display.", DEFAULT_PROP_TEXT,
373           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
374   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
375       g_param_spec_boolean ("shaded-background", "shaded background",
376           "Whether to shade the background under the text area",
377           DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
378   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING_VALUE,
379       g_param_spec_uint ("shading-value", "background shading value",
380           "Shading value to apply if shaded-background is true", 1, 255,
381           DEFAULT_PROP_SHADING_VALUE,
382           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
383   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
384       g_param_spec_enum ("valignment", "vertical alignment",
385           "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
386           DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
387   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
388       g_param_spec_enum ("halignment", "horizontal alignment",
389           "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
390           DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
391   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
392       g_param_spec_int ("xpad", "horizontal paddding",
393           "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
394           DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
395   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
396       g_param_spec_int ("ypad", "vertical padding",
397           "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
398           DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
399   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
400       g_param_spec_int ("deltax", "X position modifier",
401           "Shift X position to the left or to the right. Unit is pixels.",
402           G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
403           GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
404   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
405       g_param_spec_int ("deltay", "Y position modifier",
406           "Shift Y position up or down. Unit is pixels.",
407           G_MININT, G_MAXINT, DEFAULT_PROP_DELTAY,
408           GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
409 
410   /**
411    * GstBaseTextOverlay:text-x:
412    *
413    * Resulting X position of font rendering.
414    */
415   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_X,
416       g_param_spec_int ("text-x", "horizontal position.",
417           "Resulting X position of font rendering.", -G_MAXINT,
418           G_MAXINT, DEFAULT_PROP_TEXT_X, G_PARAM_READABLE));
419 
420   /**
421    * GstBaseTextOverlay:text-y:
422    *
423    * Resulting Y position of font rendering.
424    */
425   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_Y,
426       g_param_spec_int ("text-y", "vertical position",
427           "Resulting X position of font rendering.", -G_MAXINT,
428           G_MAXINT, DEFAULT_PROP_TEXT_Y, G_PARAM_READABLE));
429 
430   /**
431    * GstBaseTextOverlay:text-width:
432    *
433    * Resulting width of font rendering.
434    */
435   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_WIDTH,
436       g_param_spec_uint ("text-width", "width",
437           "Resulting width of font rendering",
438           0, G_MAXINT, DEFAULT_PROP_TEXT_WIDTH, G_PARAM_READABLE));
439 
440   /**
441    * GstBaseTextOverlay:text-height:
442    *
443    * Resulting height of font rendering.
444    */
445   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_HEIGHT,
446       g_param_spec_uint ("text-height", "height",
447           "Resulting height of font rendering", 0,
448           G_MAXINT, DEFAULT_PROP_TEXT_HEIGHT, G_PARAM_READABLE));
449 
450   /**
451    * GstBaseTextOverlay:xpos:
452    *
453    * Horizontal position of the rendered text when using positioned alignment.
454    */
455   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
456       g_param_spec_double ("xpos", "horizontal position",
457           "Horizontal position when using clamped position alignment", 0, 1.0,
458           DEFAULT_PROP_XPOS,
459           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
460   /**
461    * GstBaseTextOverlay:ypos:
462    *
463    * Vertical position of the rendered text when using positioned alignment.
464    */
465   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
466       g_param_spec_double ("ypos", "vertical position",
467           "Vertical position when using clamped position alignment", 0, 1.0,
468           DEFAULT_PROP_YPOS,
469           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
470 
471   /**
472    * GstBaseTextOverlay:x-absolute:
473    *
474    * Horizontal position of the rendered text when using absolute alignment.
475    *
476    * Maps the text area to be exactly inside of video canvas for [0, 0] - [1, 1]:
477    *
478    * [0, 0]: Top-Lefts of video and text are aligned
479    * [0.5, 0.5]: Centers are aligned
480    * [1, 1]: Bottom-Rights are aligned
481    *
482    * Values beyond [0, 0] - [1, 1] place the text outside of the video canvas.
483    *
484    * Since: 1.8
485    */
486   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X_ABSOLUTE,
487       g_param_spec_double ("x-absolute", "horizontal position",
488           "Horizontal position when using absolute alignment", -G_MAXDOUBLE,
489           G_MAXDOUBLE, DEFAULT_PROP_XPOS,
490           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
491   /**
492    * GstBaseTextOverlay:y-absolute:
493    *
494    * See x-absolute.
495    *
496    * Vertical position of the rendered text when using absolute alignment.
497    *
498    * Since: 1.8
499    */
500   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y_ABSOLUTE,
501       g_param_spec_double ("y-absolute", "vertical position",
502           "Vertical position when using absolute alignment", -G_MAXDOUBLE,
503           G_MAXDOUBLE, DEFAULT_PROP_YPOS,
504           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
505 
506   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
507       g_param_spec_enum ("wrap-mode", "wrap mode",
508           "Whether to wrap the text and if so how.",
509           GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
510           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
511   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
512       g_param_spec_string ("font-desc", "font description",
513           "Pango font description of font to be used for rendering. "
514           "See documentation of pango_font_description_from_string "
515           "for syntax.", DEFAULT_PROP_FONT_DESC,
516           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
517   /**
518    * GstBaseTextOverlay:color:
519    *
520    * Color of the rendered text.
521    */
522   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
523       g_param_spec_uint ("color", "Color",
524           "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
525           DEFAULT_PROP_COLOR,
526           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
527   /**
528    * GstTextOverlay:outline-color:
529    *
530    * Color of the outline of the rendered text.
531    */
532   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
533       g_param_spec_uint ("outline-color", "Text Outline Color",
534           "Color to use for outline the text (big-endian ARGB).", 0,
535           G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
536           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
537 
538   /**
539    * GstBaseTextOverlay:line-alignment:
540    *
541    * Alignment of text lines relative to each other (for multi-line text)
542    */
543   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
544       g_param_spec_enum ("line-alignment", "line alignment",
545           "Alignment of text lines relative to each other.",
546           GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
547           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
548   /**
549    * GstBaseTextOverlay:silent:
550    *
551    * If set, no text is rendered. Useful to switch off text rendering
552    * temporarily without removing the textoverlay element from the pipeline.
553    */
554   /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
555   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
556       g_param_spec_boolean ("silent", "silent",
557           "Whether to render the text string",
558           DEFAULT_PROP_SILENT,
559           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
560   /**
561    * GstBaseTextOverlay:draw-shadow:
562    *
563    * If set, a text shadow is drawn.
564    *
565    * Since: 1.6
566    */
567   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_SHADOW,
568       g_param_spec_boolean ("draw-shadow", "draw-shadow",
569           "Whether to draw shadow",
570           DEFAULT_PROP_DRAW_SHADOW,
571           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
572   /**
573    * GstBaseTextOverlay:draw-outline:
574    *
575    * If set, an outline is drawn.
576    *
577    * Since: 1.6
578    */
579   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_OUTLINE,
580       g_param_spec_boolean ("draw-outline", "draw-outline",
581           "Whether to draw outline",
582           DEFAULT_PROP_DRAW_OUTLINE,
583           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
584   /**
585    * GstBaseTextOverlay:wait-text:
586    *
587    * If set, the video will block until a subtitle is received on the text pad.
588    * If video and subtitles are sent in sync, like from the same demuxer, this
589    * property should be set.
590    */
591   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
592       g_param_spec_boolean ("wait-text", "Wait Text",
593           "Whether to wait for subtitles",
594           DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
595 
596   g_object_class_install_property (G_OBJECT_CLASS (klass),
597       PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
598           "Automatically adjust font size to screen-size.",
599           DEFAULT_PROP_AUTO_ADJUST_SIZE,
600           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
601 
602   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
603       g_param_spec_boolean ("vertical-render", "vertical render",
604           "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
605           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
606 
607   /**
608    * GstBaseTextOverlay:scale-mode:
609    *
610    * Scale text to compensate for and avoid distortion by subsequent video scaling
611    *
612    * Since: 1.14
613    */
614   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SCALE_MODE,
615       g_param_spec_enum ("scale-mode", "scale mode",
616           "Scale text to compensate for and avoid distortion by subsequent video scaling.",
617           GST_TYPE_BASE_TEXT_OVERLAY_SCALE_MODE, DEFAULT_PROP_SCALE_MODE,
618           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
619 
620   /**
621    * GstBaseTextOverlay:scale-pixel-aspect-ratio:
622    *
623    * Video scaling pixel-aspect-ratio to compensate for in user scale-mode.
624    *
625    * Since: 1.14
626    */
627   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SCALE_PAR,
628       gst_param_spec_fraction ("scale-pixel-aspect-ratio",
629           "scale pixel aspect ratio",
630           "Pixel aspect ratio of video scale to compensate for in user scale-mode",
631           1, 100, 100, 1, DEFAULT_PROP_SCALE_PAR_N, DEFAULT_PROP_SCALE_PAR_D,
632           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
633 }
634 
635 static void
gst_base_text_overlay_finalize(GObject * object)636 gst_base_text_overlay_finalize (GObject * object)
637 {
638   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
639 
640   g_free (overlay->default_text);
641 
642   if (overlay->composition) {
643     gst_video_overlay_composition_unref (overlay->composition);
644     overlay->composition = NULL;
645   }
646 
647   if (overlay->text_image) {
648     gst_buffer_unref (overlay->text_image);
649     overlay->text_image = NULL;
650   }
651 
652   if (overlay->layout) {
653     g_object_unref (overlay->layout);
654     overlay->layout = NULL;
655   }
656 
657   if (overlay->text_buffer) {
658     gst_buffer_unref (overlay->text_buffer);
659     overlay->text_buffer = NULL;
660   }
661 
662   if (overlay->pango_context) {
663     g_object_unref (overlay->pango_context);
664     overlay->pango_context = NULL;
665   }
666 
667   g_mutex_clear (&overlay->lock);
668   g_cond_clear (&overlay->cond);
669 
670   G_OBJECT_CLASS (parent_class)->finalize (object);
671 }
672 
673 static void
gst_base_text_overlay_init(GstBaseTextOverlay * overlay,GstBaseTextOverlayClass * klass)674 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
675     GstBaseTextOverlayClass * klass)
676 {
677   GstPadTemplate *template;
678   PangoFontDescription *desc;
679   PangoFontMap *fontmap;
680 
681   fontmap = pango_cairo_font_map_new ();
682   overlay->pango_context =
683       pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
684   g_object_unref (fontmap);
685   pango_context_set_base_gravity (overlay->pango_context, PANGO_GRAVITY_SOUTH);
686 
687   /* video sink */
688   template = gst_static_pad_template_get (&video_sink_template_factory);
689   overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
690   gst_object_unref (template);
691   gst_pad_set_event_function (overlay->video_sinkpad,
692       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
693   gst_pad_set_chain_function (overlay->video_sinkpad,
694       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
695   gst_pad_set_query_function (overlay->video_sinkpad,
696       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
697   GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
698   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
699 
700   template =
701       gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
702       "text_sink");
703   if (template) {
704     /* text sink */
705     overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
706 
707     gst_pad_set_event_function (overlay->text_sinkpad,
708         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
709     gst_pad_set_chain_function (overlay->text_sinkpad,
710         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
711     gst_pad_set_link_function (overlay->text_sinkpad,
712         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
713     gst_pad_set_unlink_function (overlay->text_sinkpad,
714         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
715     gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
716   }
717 
718   /* (video) source */
719   template = gst_static_pad_template_get (&src_template_factory);
720   overlay->srcpad = gst_pad_new_from_template (template, "src");
721   gst_object_unref (template);
722   gst_pad_set_event_function (overlay->srcpad,
723       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
724   gst_pad_set_query_function (overlay->srcpad,
725       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
726   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
727 
728   overlay->layout = pango_layout_new (overlay->pango_context);
729   desc = pango_context_get_font_description (overlay->pango_context);
730   gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
731 
732   overlay->color = DEFAULT_PROP_COLOR;
733   overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
734   overlay->halign = DEFAULT_PROP_HALIGNMENT;
735   overlay->valign = DEFAULT_PROP_VALIGNMENT;
736   overlay->xpad = DEFAULT_PROP_XPAD;
737   overlay->ypad = DEFAULT_PROP_YPAD;
738   overlay->deltax = DEFAULT_PROP_DELTAX;
739   overlay->deltay = DEFAULT_PROP_DELTAY;
740   overlay->xpos = DEFAULT_PROP_XPOS;
741   overlay->ypos = DEFAULT_PROP_YPOS;
742 
743   overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
744 
745   overlay->want_shading = DEFAULT_PROP_SHADING;
746   overlay->shading_value = DEFAULT_PROP_SHADING_VALUE;
747   overlay->silent = DEFAULT_PROP_SILENT;
748   overlay->draw_shadow = DEFAULT_PROP_DRAW_SHADOW;
749   overlay->draw_outline = DEFAULT_PROP_DRAW_OUTLINE;
750   overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
751   overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
752 
753   overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
754   overlay->need_render = TRUE;
755   overlay->text_image = NULL;
756   overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
757   overlay->scale_mode = DEFAULT_PROP_SCALE_MODE;
758   overlay->scale_par_n = DEFAULT_PROP_SCALE_PAR_N;
759   overlay->scale_par_d = DEFAULT_PROP_SCALE_PAR_D;
760 
761   overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
762   pango_layout_set_alignment (overlay->layout,
763       (PangoAlignment) overlay->line_align);
764 
765   overlay->text_buffer = NULL;
766   overlay->text_linked = FALSE;
767 
768   overlay->composition = NULL;
769   overlay->upstream_composition = NULL;
770 
771   overlay->width = 1;
772   overlay->height = 1;
773 
774   overlay->window_width = 1;
775   overlay->window_height = 1;
776 
777   overlay->text_width = DEFAULT_PROP_TEXT_WIDTH;
778   overlay->text_height = DEFAULT_PROP_TEXT_HEIGHT;
779 
780   overlay->text_x = DEFAULT_PROP_TEXT_X;
781   overlay->text_y = DEFAULT_PROP_TEXT_Y;
782 
783   overlay->render_width = 1;
784   overlay->render_height = 1;
785   overlay->render_scale = 1.0l;
786 
787   g_mutex_init (&overlay->lock);
788   g_cond_init (&overlay->cond);
789   gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
790 }
791 
792 static void
gst_base_text_overlay_set_wrap_mode(GstBaseTextOverlay * overlay,gint width)793 gst_base_text_overlay_set_wrap_mode (GstBaseTextOverlay * overlay, gint width)
794 {
795   if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
796     GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
797     pango_layout_set_width (overlay->layout, -1);
798   } else {
799     width = width * PANGO_SCALE;
800 
801     GST_DEBUG_OBJECT (overlay, "Set layout width %d", width);
802     GST_DEBUG_OBJECT (overlay, "Set wrap mode    %d", overlay->wrap_mode);
803     pango_layout_set_width (overlay->layout, width);
804   }
805 
806   pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
807 }
808 
809 static gboolean
gst_base_text_overlay_setcaps_txt(GstBaseTextOverlay * overlay,GstCaps * caps)810 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
811 {
812   GstStructure *structure;
813   const gchar *format;
814 
815   structure = gst_caps_get_structure (caps, 0);
816   format = gst_structure_get_string (structure, "format");
817   overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
818 
819   return TRUE;
820 }
821 
822 /* only negotiate/query video overlay composition support for now */
823 static gboolean
gst_base_text_overlay_negotiate(GstBaseTextOverlay * overlay,GstCaps * caps)824 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay, GstCaps * caps)
825 {
826   gboolean upstream_has_meta = FALSE;
827   gboolean caps_has_meta = FALSE;
828   gboolean alloc_has_meta = FALSE;
829   gboolean attach = FALSE;
830   gboolean ret = TRUE;
831   guint width, height;
832   GstCapsFeatures *f;
833   GstCaps *overlay_caps;
834   GstQuery *query;
835   guint alloc_index;
836 
837   GST_DEBUG_OBJECT (overlay, "performing negotiation");
838 
839   /* Clear any pending reconfigure to avoid negotiating twice */
840   gst_pad_check_reconfigure (overlay->srcpad);
841 
842   if (!caps)
843     caps = gst_pad_get_current_caps (overlay->video_sinkpad);
844   else
845     gst_caps_ref (caps);
846 
847   if (!caps || gst_caps_is_empty (caps))
848     goto no_format;
849 
850   /* Check if upstream caps have meta */
851   if ((f = gst_caps_get_features (caps, 0))) {
852     upstream_has_meta = gst_caps_features_contains (f,
853         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
854   }
855 
856   /* Initialize dimensions */
857   width = overlay->width;
858   height = overlay->height;
859 
860   if (upstream_has_meta) {
861     overlay_caps = gst_caps_ref (caps);
862   } else {
863     GstCaps *peercaps;
864 
865     /* BaseTransform requires caps for the allocation query to work */
866     overlay_caps = gst_caps_copy (caps);
867     f = gst_caps_get_features (overlay_caps, 0);
868     gst_caps_features_add (f,
869         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
870 
871     /* Then check if downstream accept overlay composition in caps */
872     /* FIXME: We should probably check if downstream *prefers* the
873      * overlay meta, and only enforce usage of it if we can't handle
874      * the format ourselves and thus would have to drop the overlays.
875      * Otherwise we should prefer what downstream wants here.
876      */
877     peercaps = gst_pad_peer_query_caps (overlay->srcpad, overlay_caps);
878     caps_has_meta = !gst_caps_is_empty (peercaps);
879     gst_caps_unref (peercaps);
880 
881     GST_DEBUG_OBJECT (overlay, "caps have overlay meta %d", caps_has_meta);
882   }
883 
884   if (upstream_has_meta || caps_has_meta) {
885     /* Send caps immediatly, it's needed by GstBaseTransform to get a reply
886      * from allocation query */
887     ret = gst_pad_set_caps (overlay->srcpad, overlay_caps);
888 
889     /* First check if the allocation meta has compositon */
890     query = gst_query_new_allocation (overlay_caps, FALSE);
891 
892     if (!gst_pad_peer_query (overlay->srcpad, query)) {
893       /* no problem, we use the query defaults */
894       GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
895 
896       /* In case we were flushing, mark reconfigure and fail this method,
897        * will make it retry */
898       if (overlay->video_flushing)
899         ret = FALSE;
900     }
901 
902     alloc_has_meta = gst_query_find_allocation_meta (query,
903         GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, &alloc_index);
904 
905     GST_DEBUG_OBJECT (overlay, "sink alloc has overlay meta %d",
906         alloc_has_meta);
907 
908     if (alloc_has_meta) {
909       const GstStructure *params;
910 
911       gst_query_parse_nth_allocation_meta (query, alloc_index, &params);
912       if (params) {
913         if (gst_structure_get (params, "width", G_TYPE_UINT, &width,
914                 "height", G_TYPE_UINT, &height, NULL)) {
915           GST_DEBUG_OBJECT (overlay, "received window size: %dx%d", width,
916               height);
917           g_assert (width != 0 && height != 0);
918         }
919       }
920     }
921 
922     gst_query_unref (query);
923   }
924 
925   /* Update render size if needed */
926   overlay->window_width = width;
927   overlay->window_height = height;
928   gst_base_text_overlay_update_render_size (overlay);
929 
930   /* For backward compatibility, we will prefer blitting if downstream
931    * allocation does not support the meta. In other case we will prefer
932    * attaching, and will fail the negotiation in the unlikely case we are
933    * force to blit, but format isn't supported. */
934 
935   if (upstream_has_meta) {
936     attach = TRUE;
937   } else if (caps_has_meta) {
938     if (alloc_has_meta) {
939       attach = TRUE;
940     } else {
941       /* Don't attach unless we cannot handle the format */
942       attach = !gst_base_text_overlay_can_handle_caps (caps);
943     }
944   } else {
945     ret = gst_base_text_overlay_can_handle_caps (caps);
946   }
947 
948   /* If we attach, then pick the overlay caps */
949   if (attach) {
950     GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, overlay_caps);
951     /* Caps where already sent */
952   } else if (ret) {
953     GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps);
954     ret = gst_pad_set_caps (overlay->srcpad, caps);
955   }
956 
957   overlay->attach_compo_to_buffer = attach;
958 
959   if (!ret) {
960     GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure");
961     gst_pad_mark_reconfigure (overlay->srcpad);
962   }
963 
964   gst_caps_unref (overlay_caps);
965   gst_caps_unref (caps);
966 
967   return ret;
968 
969 no_format:
970   {
971     if (caps)
972       gst_caps_unref (caps);
973     gst_pad_mark_reconfigure (overlay->srcpad);
974     return FALSE;
975   }
976 }
977 
978 static gboolean
gst_base_text_overlay_can_handle_caps(GstCaps * incaps)979 gst_base_text_overlay_can_handle_caps (GstCaps * incaps)
980 {
981   gboolean ret;
982   GstCaps *caps;
983 
984   caps = gst_static_caps_get (&sw_template_caps);
985   ret = gst_caps_is_subset (incaps, caps);
986   gst_caps_unref (caps);
987 
988   return ret;
989 }
990 
991 static gboolean
gst_base_text_overlay_setcaps(GstBaseTextOverlay * overlay,GstCaps * caps)992 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
993 {
994   GstVideoInfo info;
995   gboolean ret = FALSE;
996 
997   if (!gst_video_info_from_caps (&info, caps))
998     goto invalid_caps;
999 
1000   /* Render again if size have changed */
1001   if (GST_VIDEO_INFO_WIDTH (&info) != GST_VIDEO_INFO_WIDTH (&overlay->info) ||
1002       GST_VIDEO_INFO_HEIGHT (&info) != GST_VIDEO_INFO_HEIGHT (&overlay->info))
1003     overlay->need_render = TRUE;
1004 
1005   overlay->info = info;
1006   overlay->format = GST_VIDEO_INFO_FORMAT (&info);
1007   overlay->width = GST_VIDEO_INFO_WIDTH (&info);
1008   overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
1009 
1010   ret = gst_base_text_overlay_negotiate (overlay, caps);
1011 
1012   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1013 
1014   if (!overlay->attach_compo_to_buffer &&
1015       !gst_base_text_overlay_can_handle_caps (caps)) {
1016     GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps);
1017     ret = FALSE;
1018   }
1019   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1020 
1021   return ret;
1022 
1023   /* ERRORS */
1024 invalid_caps:
1025   {
1026     GST_DEBUG_OBJECT (overlay, "could not parse caps");
1027     return FALSE;
1028   }
1029 }
1030 
1031 static void
gst_base_text_overlay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1032 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
1033     const GValue * value, GParamSpec * pspec)
1034 {
1035   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
1036 
1037   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1038   switch (prop_id) {
1039     case PROP_TEXT:
1040       g_free (overlay->default_text);
1041       overlay->default_text = g_value_dup_string (value);
1042       break;
1043     case PROP_SHADING:
1044       overlay->want_shading = g_value_get_boolean (value);
1045       break;
1046     case PROP_XPAD:
1047       overlay->xpad = g_value_get_int (value);
1048       break;
1049     case PROP_YPAD:
1050       overlay->ypad = g_value_get_int (value);
1051       break;
1052     case PROP_DELTAX:
1053       overlay->deltax = g_value_get_int (value);
1054       break;
1055     case PROP_DELTAY:
1056       overlay->deltay = g_value_get_int (value);
1057       break;
1058     case PROP_XPOS:
1059       overlay->xpos = g_value_get_double (value);
1060       break;
1061     case PROP_YPOS:
1062       overlay->ypos = g_value_get_double (value);
1063       break;
1064     case PROP_X_ABSOLUTE:
1065       overlay->xpos = g_value_get_double (value);
1066       break;
1067     case PROP_Y_ABSOLUTE:
1068       overlay->ypos = g_value_get_double (value);
1069       break;
1070     case PROP_VALIGNMENT:
1071       overlay->valign = g_value_get_enum (value);
1072       break;
1073     case PROP_HALIGNMENT:
1074       overlay->halign = g_value_get_enum (value);
1075       break;
1076     case PROP_WRAP_MODE:
1077       overlay->wrap_mode = g_value_get_enum (value);
1078       break;
1079     case PROP_FONT_DESC:
1080     {
1081       PangoFontDescription *desc;
1082       const gchar *fontdesc_str;
1083 
1084       fontdesc_str = g_value_get_string (value);
1085       desc = pango_font_description_from_string (fontdesc_str);
1086       if (desc) {
1087         GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
1088         pango_layout_set_font_description (overlay->layout, desc);
1089         gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
1090         pango_font_description_free (desc);
1091       } else {
1092         GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
1093             fontdesc_str);
1094       }
1095       break;
1096     }
1097     case PROP_COLOR:
1098       overlay->color = g_value_get_uint (value);
1099       break;
1100     case PROP_OUTLINE_COLOR:
1101       overlay->outline_color = g_value_get_uint (value);
1102       break;
1103     case PROP_SILENT:
1104       overlay->silent = g_value_get_boolean (value);
1105       break;
1106     case PROP_DRAW_SHADOW:
1107       overlay->draw_shadow = g_value_get_boolean (value);
1108       break;
1109     case PROP_DRAW_OUTLINE:
1110       overlay->draw_outline = g_value_get_boolean (value);
1111       break;
1112     case PROP_LINE_ALIGNMENT:
1113       overlay->line_align = g_value_get_enum (value);
1114       pango_layout_set_alignment (overlay->layout,
1115           (PangoAlignment) overlay->line_align);
1116       break;
1117     case PROP_WAIT_TEXT:
1118       overlay->wait_text = g_value_get_boolean (value);
1119       break;
1120     case PROP_AUTO_ADJUST_SIZE:
1121       overlay->auto_adjust_size = g_value_get_boolean (value);
1122       break;
1123     case PROP_VERTICAL_RENDER:
1124       overlay->use_vertical_render = g_value_get_boolean (value);
1125       if (overlay->use_vertical_render) {
1126         overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1127         overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1128         overlay->line_align = GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT;
1129         pango_layout_set_alignment (overlay->layout,
1130             (PangoAlignment) overlay->line_align);
1131       }
1132       break;
1133     case PROP_SCALE_MODE:
1134       overlay->scale_mode = g_value_get_enum (value);
1135       break;
1136     case PROP_SCALE_PAR:
1137       overlay->scale_par_n = gst_value_get_fraction_numerator (value);
1138       overlay->scale_par_d = gst_value_get_fraction_denominator (value);
1139       break;
1140     case PROP_SHADING_VALUE:
1141       overlay->shading_value = g_value_get_uint (value);
1142       break;
1143     default:
1144       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1145       break;
1146   }
1147 
1148   overlay->need_render = TRUE;
1149   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1150 }
1151 
1152 static void
gst_base_text_overlay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1153 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
1154     GValue * value, GParamSpec * pspec)
1155 {
1156   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
1157 
1158   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1159   switch (prop_id) {
1160     case PROP_TEXT:
1161       g_value_set_string (value, overlay->default_text);
1162       break;
1163     case PROP_SHADING:
1164       g_value_set_boolean (value, overlay->want_shading);
1165       break;
1166     case PROP_XPAD:
1167       g_value_set_int (value, overlay->xpad);
1168       break;
1169     case PROP_YPAD:
1170       g_value_set_int (value, overlay->ypad);
1171       break;
1172     case PROP_DELTAX:
1173       g_value_set_int (value, overlay->deltax);
1174       break;
1175     case PROP_DELTAY:
1176       g_value_set_int (value, overlay->deltay);
1177       break;
1178     case PROP_XPOS:
1179       g_value_set_double (value, overlay->xpos);
1180       break;
1181     case PROP_YPOS:
1182       g_value_set_double (value, overlay->ypos);
1183       break;
1184     case PROP_X_ABSOLUTE:
1185       g_value_set_double (value, overlay->xpos);
1186       break;
1187     case PROP_Y_ABSOLUTE:
1188       g_value_set_double (value, overlay->ypos);
1189       break;
1190     case PROP_VALIGNMENT:
1191       g_value_set_enum (value, overlay->valign);
1192       break;
1193     case PROP_HALIGNMENT:
1194       g_value_set_enum (value, overlay->halign);
1195       break;
1196     case PROP_WRAP_MODE:
1197       g_value_set_enum (value, overlay->wrap_mode);
1198       break;
1199     case PROP_SILENT:
1200       g_value_set_boolean (value, overlay->silent);
1201       break;
1202     case PROP_DRAW_SHADOW:
1203       g_value_set_boolean (value, overlay->draw_shadow);
1204       break;
1205     case PROP_DRAW_OUTLINE:
1206       g_value_set_boolean (value, overlay->draw_outline);
1207       break;
1208     case PROP_LINE_ALIGNMENT:
1209       g_value_set_enum (value, overlay->line_align);
1210       break;
1211     case PROP_WAIT_TEXT:
1212       g_value_set_boolean (value, overlay->wait_text);
1213       break;
1214     case PROP_AUTO_ADJUST_SIZE:
1215       g_value_set_boolean (value, overlay->auto_adjust_size);
1216       break;
1217     case PROP_VERTICAL_RENDER:
1218       g_value_set_boolean (value, overlay->use_vertical_render);
1219       break;
1220     case PROP_SCALE_MODE:
1221       g_value_set_enum (value, overlay->scale_mode);
1222       break;
1223     case PROP_SCALE_PAR:
1224       gst_value_set_fraction (value, overlay->scale_par_n,
1225           overlay->scale_par_d);
1226       break;
1227     case PROP_COLOR:
1228       g_value_set_uint (value, overlay->color);
1229       break;
1230     case PROP_OUTLINE_COLOR:
1231       g_value_set_uint (value, overlay->outline_color);
1232       break;
1233     case PROP_SHADING_VALUE:
1234       g_value_set_uint (value, overlay->shading_value);
1235       break;
1236     case PROP_FONT_DESC:
1237     {
1238       const PangoFontDescription *desc;
1239 
1240       desc = pango_layout_get_font_description (overlay->layout);
1241       if (!desc)
1242         g_value_set_string (value, "");
1243       else {
1244         g_value_take_string (value, pango_font_description_to_string (desc));
1245       }
1246       break;
1247     }
1248     case PROP_TEXT_X:
1249       g_value_set_int (value, overlay->text_x);
1250       break;
1251     case PROP_TEXT_Y:
1252       g_value_set_int (value, overlay->text_y);
1253       break;
1254     case PROP_TEXT_WIDTH:
1255       g_value_set_uint (value, overlay->text_width);
1256       break;
1257     case PROP_TEXT_HEIGHT:
1258       g_value_set_uint (value, overlay->text_height);
1259       break;
1260     default:
1261       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1262       break;
1263   }
1264 
1265   overlay->need_render = TRUE;
1266   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1267 }
1268 
1269 static gboolean
gst_base_text_overlay_src_query(GstPad * pad,GstObject * parent,GstQuery * query)1270 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1271     GstQuery * query)
1272 {
1273   gboolean ret = FALSE;
1274   GstBaseTextOverlay *overlay;
1275 
1276   overlay = GST_BASE_TEXT_OVERLAY (parent);
1277 
1278   switch (GST_QUERY_TYPE (query)) {
1279     case GST_QUERY_CAPS:
1280     {
1281       GstCaps *filter, *caps;
1282 
1283       gst_query_parse_caps (query, &filter);
1284       caps = gst_base_text_overlay_get_src_caps (pad, overlay, filter);
1285       gst_query_set_caps_result (query, caps);
1286       gst_caps_unref (caps);
1287       ret = TRUE;
1288       break;
1289     }
1290     default:
1291       ret = gst_pad_query_default (pad, parent, query);
1292       break;
1293   }
1294 
1295   return ret;
1296 }
1297 
1298 static void
gst_base_text_overlay_update_render_size(GstBaseTextOverlay * overlay)1299 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay)
1300 {
1301   gdouble video_aspect = (gdouble) overlay->width / (gdouble) overlay->height;
1302   gdouble window_aspect = (gdouble) overlay->window_width /
1303       (gdouble) overlay->window_height;
1304 
1305   guint text_buffer_width = 0;
1306   guint text_buffer_height = 0;
1307 
1308   if (video_aspect >= window_aspect) {
1309     text_buffer_width = overlay->window_width;
1310     text_buffer_height = window_aspect * overlay->window_height / video_aspect;
1311   } else if (video_aspect < window_aspect) {
1312     text_buffer_width = video_aspect * overlay->window_width / window_aspect;
1313     text_buffer_height = overlay->window_height;
1314   }
1315 
1316   if ((overlay->render_width == text_buffer_width) &&
1317       (overlay->render_height == text_buffer_height))
1318     return;
1319 
1320   overlay->need_render = TRUE;
1321   overlay->render_width = text_buffer_width;
1322   overlay->render_height = text_buffer_height;
1323   overlay->render_scale = (gdouble) overlay->render_width /
1324       (gdouble) overlay->width;
1325 
1326   GST_DEBUG ("updating render dimensions %dx%d from stream %dx%d, window %dx%d "
1327       "and render scale %f", overlay->render_width, overlay->render_height,
1328       overlay->width, overlay->height, overlay->window_width,
1329       overlay->window_height, overlay->render_scale);
1330 }
1331 
1332 static gboolean
gst_base_text_overlay_src_event(GstPad * pad,GstObject * parent,GstEvent * event)1333 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1334     GstEvent * event)
1335 {
1336   GstBaseTextOverlay *overlay;
1337   gboolean ret;
1338 
1339   overlay = GST_BASE_TEXT_OVERLAY (parent);
1340 
1341   if (overlay->text_linked) {
1342     gst_event_ref (event);
1343     ret = gst_pad_push_event (overlay->video_sinkpad, event);
1344     gst_pad_push_event (overlay->text_sinkpad, event);
1345   } else {
1346     ret = gst_pad_push_event (overlay->video_sinkpad, event);
1347   }
1348 
1349   return ret;
1350 }
1351 
1352 /* gst_base_text_overlay_add_feature_and_intersect:
1353  *
1354  * Creates a new #GstCaps containing the (given caps +
1355  * given caps feature) + (given caps intersected by the
1356  * given filter).
1357  *
1358  * Returns: the new #GstCaps
1359  */
1360 static GstCaps *
gst_base_text_overlay_add_feature_and_intersect(GstCaps * caps,const gchar * feature,GstCaps * filter)1361 gst_base_text_overlay_add_feature_and_intersect (GstCaps * caps,
1362     const gchar * feature, GstCaps * filter)
1363 {
1364   int i, caps_size;
1365   GstCaps *new_caps;
1366 
1367   new_caps = gst_caps_copy (caps);
1368 
1369   caps_size = gst_caps_get_size (new_caps);
1370   for (i = 0; i < caps_size; i++) {
1371     GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
1372 
1373     if (!gst_caps_features_is_any (features)) {
1374       gst_caps_features_add (features, feature);
1375     }
1376   }
1377 
1378   gst_caps_append (new_caps, gst_caps_intersect_full (caps,
1379           filter, GST_CAPS_INTERSECT_FIRST));
1380 
1381   return new_caps;
1382 }
1383 
1384 /* gst_base_text_overlay_intersect_by_feature:
1385  *
1386  * Creates a new #GstCaps based on the following filtering rule.
1387  *
1388  * For each individual caps contained in given caps, if the
1389  * caps uses the given caps feature, keep a version of the caps
1390  * with the feature and an another one without. Otherwise, intersect
1391  * the caps with the given filter.
1392  *
1393  * Returns: the new #GstCaps
1394  */
1395 static GstCaps *
gst_base_text_overlay_intersect_by_feature(GstCaps * caps,const gchar * feature,GstCaps * filter)1396 gst_base_text_overlay_intersect_by_feature (GstCaps * caps,
1397     const gchar * feature, GstCaps * filter)
1398 {
1399   int i, caps_size;
1400   GstCaps *new_caps;
1401 
1402   new_caps = gst_caps_new_empty ();
1403 
1404   caps_size = gst_caps_get_size (caps);
1405   for (i = 0; i < caps_size; i++) {
1406     GstStructure *caps_structure = gst_caps_get_structure (caps, i);
1407     GstCapsFeatures *caps_features =
1408         gst_caps_features_copy (gst_caps_get_features (caps, i));
1409     GstCaps *filtered_caps;
1410     GstCaps *simple_caps =
1411         gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
1412     gst_caps_set_features (simple_caps, 0, caps_features);
1413 
1414     if (gst_caps_features_contains (caps_features, feature)) {
1415       gst_caps_append (new_caps, gst_caps_copy (simple_caps));
1416 
1417       gst_caps_features_remove (caps_features, feature);
1418       filtered_caps = gst_caps_ref (simple_caps);
1419     } else {
1420       filtered_caps = gst_caps_intersect_full (simple_caps, filter,
1421           GST_CAPS_INTERSECT_FIRST);
1422     }
1423 
1424     gst_caps_unref (simple_caps);
1425     gst_caps_append (new_caps, filtered_caps);
1426   }
1427 
1428   return new_caps;
1429 }
1430 
1431 static GstCaps *
gst_base_text_overlay_get_videosink_caps(GstPad * pad,GstBaseTextOverlay * overlay,GstCaps * filter)1432 gst_base_text_overlay_get_videosink_caps (GstPad * pad,
1433     GstBaseTextOverlay * overlay, GstCaps * filter)
1434 {
1435   GstPad *srcpad = overlay->srcpad;
1436   GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1437 
1438   if (filter) {
1439     /* filter caps + composition feature + filter caps
1440      * filtered by the software caps. */
1441     GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1442     overlay_filter = gst_base_text_overlay_add_feature_and_intersect (filter,
1443         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1444     gst_caps_unref (sw_caps);
1445 
1446     GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
1447         overlay_filter);
1448   }
1449 
1450   peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
1451 
1452   if (overlay_filter)
1453     gst_caps_unref (overlay_filter);
1454 
1455   if (peer_caps) {
1456 
1457     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
1458 
1459     if (gst_caps_is_any (peer_caps)) {
1460       /* if peer returns ANY caps, return filtered src pad template caps */
1461       caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
1462     } else {
1463 
1464       /* duplicate caps which contains the composition into one version with
1465        * the meta and one without. Filter the other caps by the software caps */
1466       GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1467       caps = gst_base_text_overlay_intersect_by_feature (peer_caps,
1468           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1469       gst_caps_unref (sw_caps);
1470     }
1471 
1472     gst_caps_unref (peer_caps);
1473 
1474   } else {
1475     /* no peer, our padtemplate is enough then */
1476     caps = gst_pad_get_pad_template_caps (pad);
1477   }
1478 
1479   if (filter) {
1480     GstCaps *intersection = gst_caps_intersect_full (filter, caps,
1481         GST_CAPS_INTERSECT_FIRST);
1482     gst_caps_unref (caps);
1483     caps = intersection;
1484   }
1485 
1486   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
1487 
1488   return caps;
1489 }
1490 
1491 static GstCaps *
gst_base_text_overlay_get_src_caps(GstPad * pad,GstBaseTextOverlay * overlay,GstCaps * filter)1492 gst_base_text_overlay_get_src_caps (GstPad * pad, GstBaseTextOverlay * overlay,
1493     GstCaps * filter)
1494 {
1495   GstPad *sinkpad = overlay->video_sinkpad;
1496   GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1497 
1498   if (filter) {
1499     /* duplicate filter caps which contains the composition into one version
1500      * with the meta and one without. Filter the other caps by the software
1501      * caps */
1502     GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1503     overlay_filter =
1504         gst_base_text_overlay_intersect_by_feature (filter,
1505         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1506     gst_caps_unref (sw_caps);
1507   }
1508 
1509   peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
1510 
1511   if (overlay_filter)
1512     gst_caps_unref (overlay_filter);
1513 
1514   if (peer_caps) {
1515 
1516     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
1517 
1518     if (gst_caps_is_any (peer_caps)) {
1519 
1520       /* if peer returns ANY caps, return filtered sink pad template caps */
1521       caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
1522 
1523     } else {
1524 
1525       /* return upstream caps + composition feature + upstream caps
1526        * filtered by the software caps. */
1527       GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1528       caps = gst_base_text_overlay_add_feature_and_intersect (peer_caps,
1529           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1530       gst_caps_unref (sw_caps);
1531     }
1532 
1533     gst_caps_unref (peer_caps);
1534 
1535   } else {
1536     /* no peer, our padtemplate is enough then */
1537     caps = gst_pad_get_pad_template_caps (pad);
1538   }
1539 
1540   if (filter) {
1541     GstCaps *intersection;
1542 
1543     intersection =
1544         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1545     gst_caps_unref (caps);
1546     caps = intersection;
1547   }
1548   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
1549 
1550   return caps;
1551 }
1552 
1553 static void
gst_base_text_overlay_adjust_values_with_fontdesc(GstBaseTextOverlay * overlay,PangoFontDescription * desc)1554 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1555     PangoFontDescription * desc)
1556 {
1557   gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1558   overlay->shadow_offset = (double) (font_size) / 13.0;
1559   overlay->outline_offset = (double) (font_size) / 15.0;
1560   if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1561     overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1562 }
1563 
1564 static void
gst_base_text_overlay_get_pos(GstBaseTextOverlay * overlay,gint * xpos,gint * ypos)1565 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1566     gint * xpos, gint * ypos)
1567 {
1568   gint width, height;
1569 
1570   width = overlay->logical_rect.width;
1571   height = overlay->logical_rect.height;
1572 
1573   *xpos = overlay->ink_rect.x - overlay->logical_rect.x;
1574   switch (overlay->halign) {
1575     case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1576       *xpos += overlay->xpad;
1577       *xpos = MAX (0, *xpos);
1578       break;
1579     case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1580       *xpos += (overlay->width - width) / 2;
1581       break;
1582     case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1583       *xpos += overlay->width - width - overlay->xpad;
1584       *xpos = MIN (overlay->width - overlay->ink_rect.width, *xpos);
1585       break;
1586     case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1587       *xpos += (gint) (overlay->width * overlay->xpos) - width / 2;
1588       *xpos = CLAMP (*xpos, 0, overlay->width - overlay->ink_rect.width);
1589       if (*xpos < 0)
1590         *xpos = 0;
1591       break;
1592     case GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE:
1593       *xpos = (overlay->width - overlay->text_width) * overlay->xpos;
1594       break;
1595     default:
1596       *xpos = 0;
1597   }
1598   *xpos += overlay->deltax;
1599 
1600   *ypos = overlay->ink_rect.y - overlay->logical_rect.y;
1601   switch (overlay->valign) {
1602     case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1603       /* This will be the same as baseline, if there is enough padding,
1604        * otherwise it will avoid clipping the text */
1605       *ypos += overlay->height - height - overlay->ypad;
1606       *ypos = MIN (overlay->height - overlay->ink_rect.height, *ypos);
1607       break;
1608     case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1609       *ypos += overlay->height - height - overlay->ypad;
1610       /* Don't clip, this would not respect the base line */
1611       break;
1612     case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1613       *ypos += overlay->ypad;
1614       *ypos = MAX (0.0, *ypos);
1615       break;
1616     case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1617       *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1618       *ypos = CLAMP (*ypos, 0, overlay->height - overlay->ink_rect.height);
1619       break;
1620     case GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE:
1621       *ypos = (overlay->height - overlay->text_height) * overlay->ypos;
1622       break;
1623     case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1624       *ypos = (overlay->height - height) / 2;
1625       break;
1626     default:
1627       *ypos = overlay->ypad;
1628       break;
1629   }
1630   *ypos += overlay->deltay;
1631 
1632   overlay->text_x = *xpos;
1633   overlay->text_y = *ypos;
1634 
1635   GST_DEBUG_OBJECT (overlay, "Placing overlay at (%d, %d)", *xpos, *ypos);
1636 }
1637 
1638 static inline void
gst_base_text_overlay_set_composition(GstBaseTextOverlay * overlay)1639 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1640 {
1641   gint xpos, ypos;
1642   GstVideoOverlayRectangle *rectangle;
1643 
1644   if (overlay->text_image && overlay->text_width != 1) {
1645     gint render_width, render_height;
1646 
1647     gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1648 
1649     render_width = overlay->ink_rect.width;
1650     render_height = overlay->ink_rect.height;
1651 
1652     GST_DEBUG ("updating composition for '%s' with window size %dx%d, "
1653         "buffer size %dx%d, render size %dx%d and position (%d, %d)",
1654         overlay->default_text, overlay->window_width, overlay->window_height,
1655         overlay->text_width, overlay->text_height, render_width,
1656         render_height, xpos, ypos);
1657 
1658     gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1659         GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1660         overlay->text_width, overlay->text_height);
1661 
1662     rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1663         xpos, ypos, render_width, render_height,
1664         GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1665 
1666     if (overlay->composition)
1667       gst_video_overlay_composition_unref (overlay->composition);
1668 
1669     if (overlay->upstream_composition) {
1670       overlay->composition =
1671           gst_video_overlay_composition_copy (overlay->upstream_composition);
1672       gst_video_overlay_composition_add_rectangle (overlay->composition,
1673           rectangle);
1674     } else {
1675       overlay->composition = gst_video_overlay_composition_new (rectangle);
1676     }
1677 
1678     gst_video_overlay_rectangle_unref (rectangle);
1679 
1680   } else if (overlay->composition) {
1681     gst_video_overlay_composition_unref (overlay->composition);
1682     overlay->composition = NULL;
1683   }
1684 }
1685 
1686 static gboolean
gst_text_overlay_filter_foreground_attr(PangoAttribute * attr,gpointer data)1687 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1688 {
1689   if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1690     return FALSE;
1691   } else {
1692     return TRUE;
1693   }
1694 }
1695 
1696 static void
gst_base_text_overlay_render_pangocairo(GstBaseTextOverlay * overlay,const gchar * string,gint textlen)1697 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1698     const gchar * string, gint textlen)
1699 {
1700   cairo_t *cr;
1701   cairo_surface_t *surface;
1702   PangoRectangle ink_rect, logical_rect;
1703   cairo_matrix_t cairo_matrix;
1704   gint unscaled_width, unscaled_height;
1705   gint width, height;
1706   gboolean full_width = FALSE;
1707   double scalef_x = 1.0, scalef_y = 1.0;
1708   double a, r, g, b;
1709   gdouble shadow_offset = 0.0;
1710   gdouble outline_offset = 0.0;
1711   gint xpad = 0, ypad = 0;
1712   GstBuffer *buffer;
1713   GstMapInfo map;
1714 
1715   if (overlay->auto_adjust_size) {
1716     /* 640 pixel is default */
1717     scalef_x = scalef_y = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1718   }
1719 
1720   if (overlay->scale_mode != GST_BASE_TEXT_OVERLAY_SCALE_MODE_NONE) {
1721     gint par_n = 1, par_d = 1;
1722 
1723     switch (overlay->scale_mode) {
1724       case GST_BASE_TEXT_OVERLAY_SCALE_MODE_PAR:
1725         par_n = overlay->info.par_n;
1726         par_d = overlay->info.par_d;
1727         break;
1728       case GST_BASE_TEXT_OVERLAY_SCALE_MODE_DISPLAY:
1729         /* (width * par_n) / (height * par_d) = (display_w / display_h) */
1730         if (!gst_util_fraction_multiply (overlay->window_width,
1731                 overlay->window_height, overlay->height, overlay->width,
1732                 &par_n, &par_d)) {
1733           GST_WARNING_OBJECT (overlay,
1734               "Can't figure out display ratio, defaulting to 1:1");
1735           par_n = par_d = 1;
1736         }
1737         break;
1738       case GST_BASE_TEXT_OVERLAY_SCALE_MODE_USER:
1739         par_n = overlay->scale_par_n;
1740         par_d = overlay->scale_par_d;
1741         break;
1742       default:
1743         break;
1744     }
1745     /* sanitize */
1746     if (!par_n || !par_d)
1747       par_n = par_d = 1;
1748     /* compensate later scaling as would be done for a par_n / par_d p-a-r;
1749      * apply all scaling to y so as to allow for predictable text width
1750      * layout independent of the presentation aspect scaling */
1751     if (overlay->use_vertical_render) {
1752       scalef_y *= ((gdouble) par_d) / ((gdouble) par_n);
1753     } else {
1754       scalef_y *= ((gdouble) par_n) / ((gdouble) par_d);
1755     }
1756     GST_DEBUG_OBJECT (overlay,
1757         "compensate scaling mode %d par %d/%d, scale %f, %f",
1758         overlay->scale_mode, par_n, par_d, scalef_x, scalef_y);
1759   }
1760 
1761   if (overlay->draw_shadow)
1762     shadow_offset = ceil (overlay->shadow_offset);
1763 
1764   /* This value is uses as cairo line width, which is the diameter of a pen
1765    * that is circular. That's why only half of it is used to offset */
1766   if (overlay->draw_outline)
1767     outline_offset = ceil (overlay->outline_offset);
1768 
1769   if (overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_LEFT ||
1770       overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT)
1771     xpad = overlay->xpad;
1772 
1773   if (overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_TOP ||
1774       overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM)
1775     ypad = overlay->ypad;
1776 
1777   pango_layout_set_width (overlay->layout, -1);
1778   /* set text on pango layout */
1779   pango_layout_set_markup (overlay->layout, string, textlen);
1780 
1781   /* get subtitle image size */
1782   pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1783 
1784   unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1785   width = ceil (unscaled_width * scalef_x);
1786 
1787   /*
1788    * subtitle image width can be larger then overlay width
1789    * so rearrange overlay wrap mode.
1790    */
1791   if (overlay->use_vertical_render) {
1792     if (width + ypad > overlay->height) {
1793       width = overlay->height - ypad;
1794       full_width = TRUE;
1795     }
1796   } else if (width + xpad > overlay->width) {
1797     width = overlay->width - xpad;
1798     full_width = TRUE;
1799   }
1800 
1801   if (full_width) {
1802     unscaled_width = width / scalef_x;
1803     gst_base_text_overlay_set_wrap_mode (overlay,
1804         unscaled_width - shadow_offset - outline_offset);
1805     pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1806 
1807     unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1808     width = ceil (unscaled_width * scalef_x);
1809   }
1810 
1811   unscaled_height = ink_rect.height + shadow_offset + outline_offset;
1812   height = ceil (unscaled_height * scalef_y);
1813 
1814   if (overlay->use_vertical_render) {
1815     if (height + xpad > overlay->width) {
1816       height = overlay->width - xpad;
1817       unscaled_height = width / scalef_y;
1818     }
1819   } else if (height + ypad > overlay->height) {
1820     height = overlay->height - ypad;
1821     unscaled_height = height / scalef_y;
1822   }
1823 
1824   GST_DEBUG_OBJECT (overlay, "Rendering with ink rect (%d, %d) %dx%d and "
1825       "logical rect (%d, %d) %dx%d", ink_rect.x, ink_rect.y, ink_rect.width,
1826       ink_rect.height, logical_rect.x, logical_rect.y, logical_rect.width,
1827       logical_rect.height);
1828   GST_DEBUG_OBJECT (overlay, "Rendering with width %d and height %d "
1829       "(shadow %f, outline %f)", unscaled_width, unscaled_height,
1830       shadow_offset, outline_offset);
1831 
1832 
1833   /* Save and scale the rectangles so get_pos() can place the text */
1834   overlay->ink_rect.x =
1835       ceil ((ink_rect.x - ceil (outline_offset / 2.0l)) * scalef_x);
1836   overlay->ink_rect.y =
1837       ceil ((ink_rect.y - ceil (outline_offset / 2.0l)) * scalef_y);
1838   overlay->ink_rect.width = width;
1839   overlay->ink_rect.height = height;
1840 
1841   overlay->logical_rect.x =
1842       ceil ((logical_rect.x - ceil (outline_offset / 2.0l)) * scalef_x);
1843   overlay->logical_rect.y =
1844       ceil ((logical_rect.y - ceil (outline_offset / 2.0l)) * scalef_y);
1845   overlay->logical_rect.width =
1846       ceil ((logical_rect.width + shadow_offset + outline_offset) * scalef_x);
1847   overlay->logical_rect.height =
1848       ceil ((logical_rect.height + shadow_offset + outline_offset) * scalef_y);
1849 
1850   /* flip the rectangle if doing vertical render */
1851   if (overlay->use_vertical_render) {
1852     PangoRectangle tmp = overlay->ink_rect;
1853 
1854     overlay->ink_rect.x = tmp.y;
1855     overlay->ink_rect.y = tmp.x;
1856     overlay->ink_rect.width = tmp.height;
1857     overlay->ink_rect.height = tmp.width;
1858     /* We want the top left corect, but we now have the top right */
1859     overlay->ink_rect.x += overlay->ink_rect.width;
1860 
1861     tmp = overlay->logical_rect;
1862     overlay->logical_rect.x = tmp.y;
1863     overlay->logical_rect.y = tmp.x;
1864     overlay->logical_rect.width = tmp.height;
1865     overlay->logical_rect.height = tmp.width;
1866     overlay->logical_rect.x += overlay->logical_rect.width;
1867   }
1868 
1869   /* scale to reported window size */
1870   width = ceil (width * overlay->render_scale);
1871   height = ceil (height * overlay->render_scale);
1872   scalef_x *= overlay->render_scale;
1873   scalef_y *= overlay->render_scale;
1874 
1875   if (width <= 0 || height <= 0) {
1876     GST_DEBUG_OBJECT (overlay,
1877         "Overlay is outside video frame. Skipping text rendering");
1878     return;
1879   }
1880 
1881   if (unscaled_height <= 0 || unscaled_width <= 0) {
1882     GST_DEBUG_OBJECT (overlay,
1883         "Overlay is outside video frame. Skipping text rendering");
1884     return;
1885   }
1886   /* Prepare the transformation matrix. Note that the transformation happens
1887    * in reverse order. So for horizontal text, we will translate and then
1888    * scale. This is important to understand which scale shall be used. */
1889   /* So, as this init'ed scale happens last, when the rectangle has already
1890    * been rotated, the scaling applied to text height (up to now),
1891    * has to be applied along the x-axis */
1892   if (overlay->use_vertical_render) {
1893     double tmp;
1894 
1895     tmp = scalef_x;
1896     scalef_x = scalef_y;
1897     scalef_y = tmp;
1898   }
1899   cairo_matrix_init_scale (&cairo_matrix, scalef_x, scalef_y);
1900 
1901   if (overlay->use_vertical_render) {
1902     gint tmp;
1903 
1904     /* tranlate to the center of the image, rotate, and tranlate the rotated
1905      * image back to the right place */
1906     cairo_matrix_translate (&cairo_matrix, unscaled_height / 2.0l,
1907         unscaled_width / 2.0l);
1908     /* 90 degree clockwise rotation which is PI / 2 in radiants */
1909     cairo_matrix_rotate (&cairo_matrix, G_PI_2);
1910     cairo_matrix_translate (&cairo_matrix, -(unscaled_width / 2.0l),
1911         -(unscaled_height / 2.0l));
1912 
1913     /* Swap width and height */
1914     tmp = height;
1915     height = width;
1916     width = tmp;
1917   }
1918 
1919   cairo_matrix_translate (&cairo_matrix,
1920       ceil (outline_offset / 2.0l) - ink_rect.x,
1921       ceil (outline_offset / 2.0l) - ink_rect.y);
1922 
1923   /* reallocate overlay buffer */
1924   buffer = gst_buffer_new_and_alloc (4 * width * height);
1925   gst_buffer_replace (&overlay->text_image, buffer);
1926   gst_buffer_unref (buffer);
1927 
1928   gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1929   surface = cairo_image_surface_create_for_data (map.data,
1930       CAIRO_FORMAT_ARGB32, width, height, width * 4);
1931   cr = cairo_create (surface);
1932 
1933   /* clear surface */
1934   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1935   cairo_paint (cr);
1936 
1937   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1938 
1939   /* apply transformations */
1940   cairo_set_matrix (cr, &cairo_matrix);
1941 
1942   /* FIXME: We use show_layout everywhere except for the surface
1943    * because it's really faster and internally does all kinds of
1944    * caching. Unfortunately we have to paint to a cairo path for
1945    * the outline and this is slow. Once Pango supports user fonts
1946    * we should use them, see
1947    * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1948    *
1949    * Idea would the be, to create a cairo user font that
1950    * does shadow, outline, text painting in the
1951    * render_glyph function.
1952    */
1953 
1954   /* draw shadow text */
1955   if (overlay->draw_shadow) {
1956     PangoAttrList *origin_attr, *filtered_attr, *temp_attr;
1957 
1958     /* Store a ref on the original attributes for later restoration */
1959     origin_attr =
1960         pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1961     /* Take a copy of the original attributes, because pango_attr_list_filter
1962      * modifies the passed list */
1963     temp_attr = pango_attr_list_copy (origin_attr);
1964     filtered_attr =
1965         pango_attr_list_filter (temp_attr,
1966         gst_text_overlay_filter_foreground_attr, NULL);
1967     pango_attr_list_unref (temp_attr);
1968 
1969     cairo_save (cr);
1970     cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1971     cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1972     pango_layout_set_attributes (overlay->layout, filtered_attr);
1973     pango_cairo_show_layout (cr, overlay->layout);
1974     pango_layout_set_attributes (overlay->layout, origin_attr);
1975     pango_attr_list_unref (filtered_attr);
1976     pango_attr_list_unref (origin_attr);
1977     cairo_restore (cr);
1978   }
1979 
1980   /* draw outline text */
1981   if (overlay->draw_outline) {
1982     a = (overlay->outline_color >> 24) & 0xff;
1983     r = (overlay->outline_color >> 16) & 0xff;
1984     g = (overlay->outline_color >> 8) & 0xff;
1985     b = (overlay->outline_color >> 0) & 0xff;
1986 
1987     cairo_save (cr);
1988     cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1989     cairo_set_line_width (cr, overlay->outline_offset);
1990     pango_cairo_layout_path (cr, overlay->layout);
1991     cairo_stroke (cr);
1992     cairo_restore (cr);
1993   }
1994 
1995   a = (overlay->color >> 24) & 0xff;
1996   r = (overlay->color >> 16) & 0xff;
1997   g = (overlay->color >> 8) & 0xff;
1998   b = (overlay->color >> 0) & 0xff;
1999 
2000   /* draw text */
2001   cairo_save (cr);
2002   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
2003   pango_cairo_show_layout (cr, overlay->layout);
2004   cairo_restore (cr);
2005 
2006   cairo_destroy (cr);
2007   cairo_surface_destroy (surface);
2008   gst_buffer_unmap (buffer, &map);
2009   if (width != 0)
2010     overlay->text_width = width;
2011   if (height != 0)
2012     overlay->text_height = height;
2013 
2014   gst_base_text_overlay_set_composition (overlay);
2015 }
2016 
2017 static inline void
gst_base_text_overlay_shade_planar_Y(GstBaseTextOverlay * overlay,GstVideoFrame * dest,gint x0,gint x1,gint y0,gint y1)2018 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
2019     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
2020 {
2021   gint i, j, dest_stride;
2022   guint8 *dest_ptr;
2023 
2024   dest_stride = dest->info.stride[0];
2025   dest_ptr = dest->data[0];
2026 
2027   for (i = y0; i < y1; ++i) {
2028     for (j = x0; j < x1; ++j) {
2029       gint y = dest_ptr[(i * dest_stride) + j] - overlay->shading_value;
2030 
2031       dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
2032     }
2033   }
2034 }
2035 
2036 static inline void
gst_base_text_overlay_shade_packed_Y(GstBaseTextOverlay * overlay,GstVideoFrame * dest,gint x0,gint x1,gint y0,gint y1)2037 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
2038     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
2039 {
2040   gint i, j;
2041   guint dest_stride, pixel_stride;
2042   guint8 *dest_ptr;
2043 
2044   dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
2045   dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
2046   pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
2047 
2048   if (x0 != 0)
2049     x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
2050   if (x1 != 0)
2051     x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
2052 
2053   if (y0 != 0)
2054     y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
2055   if (y1 != 0)
2056     y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
2057 
2058   for (i = y0; i < y1; i++) {
2059     for (j = x0; j < x1; j++) {
2060       gint y;
2061       gint y_pos;
2062 
2063       y_pos = (i * dest_stride) + j * pixel_stride;
2064       y = dest_ptr[y_pos] - overlay->shading_value;
2065 
2066       dest_ptr[y_pos] = CLAMP (y, 0, 255);
2067     }
2068   }
2069 }
2070 
2071 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
2072 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
2073 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
2074 static inline void
gst_base_text_overlay_shade_xRGB(GstBaseTextOverlay * overlay,GstVideoFrame * dest,gint x0,gint x1,gint y0,gint y1)2075 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
2076     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
2077 {
2078   gint i, j;
2079   guint8 *dest_ptr;
2080 
2081   dest_ptr = dest->data[0];
2082 
2083   for (i = y0; i < y1; i++) {
2084     for (j = x0; j < x1; j++) {
2085       gint y, y_pos, k;
2086 
2087       y_pos = (i * 4 * overlay->width) + j * 4;
2088       for (k = 0; k < 4; k++) {
2089         y = dest_ptr[y_pos + k] - overlay->shading_value;
2090         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
2091       }
2092     }
2093   }
2094 }
2095 
2096 /* FIXME: orcify */
2097 static void
gst_base_text_overlay_shade_rgb24(GstBaseTextOverlay * overlay,GstVideoFrame * frame,gint x0,gint x1,gint y0,gint y1)2098 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
2099     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2100 {
2101   const int pstride = 3;
2102   gint y, x, stride, shading_val, tmp;
2103   guint8 *p;
2104 
2105   shading_val = -overlay->shading_value;
2106   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
2107 
2108   for (y = y0; y < y1; ++y) {
2109     p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
2110     p += (y * stride) + (x0 * pstride);
2111     for (x = x0; x < x1; ++x) {
2112       tmp = *p + shading_val;
2113       *p++ = CLAMP (tmp, 0, 255);
2114       tmp = *p + shading_val;
2115       *p++ = CLAMP (tmp, 0, 255);
2116       tmp = *p + shading_val;
2117       *p++ = CLAMP (tmp, 0, 255);
2118     }
2119   }
2120 }
2121 
2122 static void
gst_base_text_overlay_shade_IYU1(GstBaseTextOverlay * overlay,GstVideoFrame * frame,gint x0,gint x1,gint y0,gint y1)2123 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
2124     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2125 {
2126   gint y, x, stride, shading_val, tmp;
2127   guint8 *p;
2128 
2129   shading_val = -overlay->shading_value;
2130   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
2131 
2132   /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
2133   for (y = y0; y < y1; ++y) {
2134     p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
2135     /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
2136     /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
2137      * unlikely anyone will notice.. */
2138     p += (y * stride) + ((x0 / 2) * 3) + 1;
2139     for (x = x0; x < x1; x += 2) {
2140       tmp = *p + shading_val;
2141       *p++ = CLAMP (tmp, 0, 255);
2142       tmp = *p + shading_val;
2143       *p++ = CLAMP (tmp, 0, 255);
2144       /* skip chroma */
2145       p++;
2146     }
2147   }
2148 }
2149 
2150 #define ARGB_SHADE_FUNCTION(name, OFFSET)	\
2151 static inline void \
2152 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
2153 gint x0, gint x1, gint y0, gint y1) \
2154 { \
2155   gint i, j;\
2156   guint8 *dest_ptr;\
2157   \
2158   dest_ptr = dest->data[0];\
2159   \
2160   for (i = y0; i < y1; i++) {\
2161     for (j = x0; j < x1; j++) {\
2162       gint y, y_pos, k;\
2163       y_pos = (i * 4 * overlay->width) + j * 4;\
2164       for (k = OFFSET; k < 3+OFFSET; k++) {\
2165         y = dest_ptr[y_pos + k] - overlay->shading_value;\
2166         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
2167       }\
2168     }\
2169   }\
2170 }
2171 ARGB_SHADE_FUNCTION (ARGB, 1);
2172 ARGB_SHADE_FUNCTION (ABGR, 1);
2173 ARGB_SHADE_FUNCTION (RGBA, 0);
2174 ARGB_SHADE_FUNCTION (BGRA, 0);
2175 
2176 static void
gst_base_text_overlay_render_text(GstBaseTextOverlay * overlay,const gchar * text,gint textlen)2177 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
2178     const gchar * text, gint textlen)
2179 {
2180   gchar *string;
2181 
2182   if (!overlay->need_render) {
2183     GST_DEBUG ("Using previously rendered text.");
2184     return;
2185   }
2186 
2187   /* -1 is the whole string */
2188   if (text != NULL && textlen < 0) {
2189     textlen = strlen (text);
2190   }
2191 
2192   if (text != NULL) {
2193     string = g_strndup (text, textlen);
2194   } else {                      /* empty string */
2195     string = g_strdup (" ");
2196   }
2197   g_strdelimit (string, "\r\t", ' ');
2198   textlen = strlen (string);
2199 
2200   /* FIXME: should we check for UTF-8 here? */
2201 
2202   GST_DEBUG ("Rendering '%s'", string);
2203   gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
2204 
2205   g_free (string);
2206 
2207   overlay->need_render = FALSE;
2208 }
2209 
2210 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
2211 #define BOX_XPAD  6
2212 #define BOX_YPAD  6
2213 
2214 static void
gst_base_text_overlay_shade_background(GstBaseTextOverlay * overlay,GstVideoFrame * frame,gint x0,gint x1,gint y0,gint y1)2215 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
2216     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2217 {
2218   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
2219   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
2220 
2221   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
2222   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
2223 
2224   switch (overlay->format) {
2225     case GST_VIDEO_FORMAT_I420:
2226     case GST_VIDEO_FORMAT_YV12:
2227     case GST_VIDEO_FORMAT_NV12:
2228     case GST_VIDEO_FORMAT_NV21:
2229     case GST_VIDEO_FORMAT_Y41B:
2230     case GST_VIDEO_FORMAT_Y42B:
2231     case GST_VIDEO_FORMAT_Y444:
2232     case GST_VIDEO_FORMAT_YUV9:
2233     case GST_VIDEO_FORMAT_YVU9:
2234     case GST_VIDEO_FORMAT_GRAY8:
2235     case GST_VIDEO_FORMAT_A420:
2236       gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
2237       break;
2238     case GST_VIDEO_FORMAT_AYUV:
2239     case GST_VIDEO_FORMAT_UYVY:
2240     case GST_VIDEO_FORMAT_VYUY:
2241     case GST_VIDEO_FORMAT_YUY2:
2242     case GST_VIDEO_FORMAT_v308:
2243     case GST_VIDEO_FORMAT_IYU2:
2244       gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
2245       break;
2246     case GST_VIDEO_FORMAT_xRGB:
2247       gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
2248       break;
2249     case GST_VIDEO_FORMAT_xBGR:
2250       gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
2251       break;
2252     case GST_VIDEO_FORMAT_BGRx:
2253       gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
2254       break;
2255     case GST_VIDEO_FORMAT_RGBx:
2256       gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
2257       break;
2258     case GST_VIDEO_FORMAT_ARGB:
2259       gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
2260       break;
2261     case GST_VIDEO_FORMAT_ABGR:
2262       gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
2263       break;
2264     case GST_VIDEO_FORMAT_RGBA:
2265       gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
2266       break;
2267     case GST_VIDEO_FORMAT_BGRA:
2268       gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
2269       break;
2270     case GST_VIDEO_FORMAT_BGR:
2271     case GST_VIDEO_FORMAT_RGB:
2272       gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
2273       break;
2274     case GST_VIDEO_FORMAT_IYU1:
2275       gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
2276       break;
2277     default:
2278       GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
2279           gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
2280       break;
2281   }
2282 }
2283 
2284 static GstFlowReturn
gst_base_text_overlay_push_frame(GstBaseTextOverlay * overlay,GstBuffer * video_frame)2285 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
2286     GstBuffer * video_frame)
2287 {
2288   GstVideoFrame frame;
2289 
2290   if (overlay->composition == NULL)
2291     goto done;
2292 
2293   if (gst_pad_check_reconfigure (overlay->srcpad)) {
2294     if (!gst_base_text_overlay_negotiate (overlay, NULL)) {
2295       gst_pad_mark_reconfigure (overlay->srcpad);
2296       gst_buffer_unref (video_frame);
2297       if (GST_PAD_IS_FLUSHING (overlay->srcpad))
2298         return GST_FLOW_FLUSHING;
2299       else
2300         return GST_FLOW_NOT_NEGOTIATED;
2301     }
2302   }
2303 
2304   video_frame = gst_buffer_make_writable (video_frame);
2305 
2306   if (overlay->attach_compo_to_buffer) {
2307     GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
2308     gst_buffer_add_video_overlay_composition_meta (video_frame,
2309         overlay->composition);
2310     /* FIXME: emulate shaded background box if want_shading=true */
2311     goto done;
2312   }
2313 
2314   if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
2315           GST_MAP_READWRITE))
2316     goto invalid_frame;
2317 
2318   /* shaded background box */
2319   if (overlay->want_shading) {
2320     gint xpos, ypos;
2321 
2322     gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
2323 
2324     gst_base_text_overlay_shade_background (overlay, &frame,
2325         xpos, xpos + overlay->text_width, ypos, ypos + overlay->text_height);
2326   }
2327 
2328   gst_video_overlay_composition_blend (overlay->composition, &frame);
2329 
2330   gst_video_frame_unmap (&frame);
2331 
2332 done:
2333 
2334   return gst_pad_push (overlay->srcpad, video_frame);
2335 
2336   /* ERRORS */
2337 invalid_frame:
2338   {
2339     gst_buffer_unref (video_frame);
2340     GST_DEBUG_OBJECT (overlay, "received invalid buffer");
2341     return GST_FLOW_OK;
2342   }
2343 }
2344 
2345 static GstPadLinkReturn
gst_base_text_overlay_text_pad_link(GstPad * pad,GstObject * parent,GstPad * peer)2346 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
2347     GstPad * peer)
2348 {
2349   GstBaseTextOverlay *overlay;
2350 
2351   overlay = GST_BASE_TEXT_OVERLAY (parent);
2352   if (G_UNLIKELY (!overlay))
2353     return GST_PAD_LINK_REFUSED;
2354 
2355   GST_DEBUG_OBJECT (overlay, "Text pad linked");
2356 
2357   overlay->text_linked = TRUE;
2358 
2359   return GST_PAD_LINK_OK;
2360 }
2361 
2362 static void
gst_base_text_overlay_text_pad_unlink(GstPad * pad,GstObject * parent)2363 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
2364 {
2365   GstBaseTextOverlay *overlay;
2366 
2367   /* don't use gst_pad_get_parent() here, will deadlock */
2368   overlay = GST_BASE_TEXT_OVERLAY (parent);
2369 
2370   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
2371 
2372   overlay->text_linked = FALSE;
2373 
2374   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
2375 }
2376 
2377 static gboolean
gst_base_text_overlay_text_event(GstPad * pad,GstObject * parent,GstEvent * event)2378 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
2379     GstEvent * event)
2380 {
2381   gboolean ret = FALSE;
2382   GstBaseTextOverlay *overlay = NULL;
2383 
2384   overlay = GST_BASE_TEXT_OVERLAY (parent);
2385 
2386   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2387 
2388   switch (GST_EVENT_TYPE (event)) {
2389     case GST_EVENT_CAPS:
2390     {
2391       GstCaps *caps;
2392 
2393       gst_event_parse_caps (event, &caps);
2394       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
2395       gst_event_unref (event);
2396       break;
2397     }
2398     case GST_EVENT_SEGMENT:
2399     {
2400       const GstSegment *segment;
2401 
2402       overlay->text_eos = FALSE;
2403 
2404       gst_event_parse_segment (event, &segment);
2405 
2406       if (segment->format == GST_FORMAT_TIME) {
2407         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2408         gst_segment_copy_into (segment, &overlay->text_segment);
2409         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
2410             &overlay->text_segment);
2411         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2412       } else {
2413         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2414             ("received non-TIME newsegment event on text input"));
2415       }
2416 
2417       gst_event_unref (event);
2418       ret = TRUE;
2419 
2420       /* wake up the video chain, it might be waiting for a text buffer or
2421        * a text segment update */
2422       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2423       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2424       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2425       break;
2426     }
2427     case GST_EVENT_GAP:
2428     {
2429       GstClockTime start, duration;
2430 
2431       gst_event_parse_gap (event, &start, &duration);
2432       if (GST_CLOCK_TIME_IS_VALID (duration))
2433         start += duration;
2434       /* we do not expect another buffer until after gap,
2435        * so that is our position now */
2436       overlay->text_segment.position = start;
2437 
2438       /* wake up the video chain, it might be waiting for a text buffer or
2439        * a text segment update */
2440       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2441       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2442       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2443 
2444       gst_event_unref (event);
2445       ret = TRUE;
2446       break;
2447     }
2448     case GST_EVENT_FLUSH_STOP:
2449       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2450       GST_INFO_OBJECT (overlay, "text flush stop");
2451       overlay->text_flushing = FALSE;
2452       overlay->text_eos = FALSE;
2453       gst_base_text_overlay_pop_text (overlay);
2454       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2455       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2456       gst_event_unref (event);
2457       ret = TRUE;
2458       break;
2459     case GST_EVENT_FLUSH_START:
2460       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2461       GST_INFO_OBJECT (overlay, "text flush start");
2462       overlay->text_flushing = TRUE;
2463       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2464       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2465       gst_event_unref (event);
2466       ret = TRUE;
2467       break;
2468     case GST_EVENT_EOS:
2469       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2470       overlay->text_eos = TRUE;
2471       GST_INFO_OBJECT (overlay, "text EOS");
2472       /* wake up the video chain, it might be waiting for a text buffer or
2473        * a text segment update */
2474       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2475       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2476       gst_event_unref (event);
2477       ret = TRUE;
2478       break;
2479     default:
2480       ret = gst_pad_event_default (pad, parent, event);
2481       break;
2482   }
2483 
2484   return ret;
2485 }
2486 
2487 static gboolean
gst_base_text_overlay_video_event(GstPad * pad,GstObject * parent,GstEvent * event)2488 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
2489     GstEvent * event)
2490 {
2491   gboolean ret = FALSE;
2492   GstBaseTextOverlay *overlay = NULL;
2493 
2494   overlay = GST_BASE_TEXT_OVERLAY (parent);
2495 
2496   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2497 
2498   switch (GST_EVENT_TYPE (event)) {
2499     case GST_EVENT_CAPS:
2500     {
2501       GstCaps *caps;
2502 
2503       gst_event_parse_caps (event, &caps);
2504       ret = gst_base_text_overlay_setcaps (overlay, caps);
2505       gst_event_unref (event);
2506       break;
2507     }
2508     case GST_EVENT_SEGMENT:
2509     {
2510       const GstSegment *segment;
2511 
2512       GST_DEBUG_OBJECT (overlay, "received new segment");
2513 
2514       gst_event_parse_segment (event, &segment);
2515 
2516       if (segment->format == GST_FORMAT_TIME) {
2517         gst_segment_copy_into (segment, &overlay->segment);
2518         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
2519             &overlay->segment);
2520       } else {
2521         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2522             ("received non-TIME newsegment event on video input"));
2523       }
2524 
2525       ret = gst_pad_event_default (pad, parent, event);
2526       break;
2527     }
2528     case GST_EVENT_EOS:
2529       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2530       GST_INFO_OBJECT (overlay, "video EOS");
2531       overlay->video_eos = TRUE;
2532       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2533       ret = gst_pad_event_default (pad, parent, event);
2534       break;
2535     case GST_EVENT_FLUSH_START:
2536       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2537       GST_INFO_OBJECT (overlay, "video flush start");
2538       overlay->video_flushing = TRUE;
2539       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2540       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2541       ret = gst_pad_event_default (pad, parent, event);
2542       break;
2543     case GST_EVENT_FLUSH_STOP:
2544       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2545       GST_INFO_OBJECT (overlay, "video flush stop");
2546       overlay->video_flushing = FALSE;
2547       overlay->video_eos = FALSE;
2548       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2549       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2550       ret = gst_pad_event_default (pad, parent, event);
2551       break;
2552     default:
2553       ret = gst_pad_event_default (pad, parent, event);
2554       break;
2555   }
2556 
2557   return ret;
2558 }
2559 
2560 static gboolean
gst_base_text_overlay_video_query(GstPad * pad,GstObject * parent,GstQuery * query)2561 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
2562     GstQuery * query)
2563 {
2564   gboolean ret = FALSE;
2565   GstBaseTextOverlay *overlay;
2566 
2567   overlay = GST_BASE_TEXT_OVERLAY (parent);
2568 
2569   switch (GST_QUERY_TYPE (query)) {
2570     case GST_QUERY_CAPS:
2571     {
2572       GstCaps *filter, *caps;
2573 
2574       gst_query_parse_caps (query, &filter);
2575       caps = gst_base_text_overlay_get_videosink_caps (pad, overlay, filter);
2576       gst_query_set_caps_result (query, caps);
2577       gst_caps_unref (caps);
2578       ret = TRUE;
2579       break;
2580     }
2581     default:
2582       ret = gst_pad_query_default (pad, parent, query);
2583       break;
2584   }
2585 
2586   return ret;
2587 }
2588 
2589 /* Called with lock held */
2590 static void
gst_base_text_overlay_pop_text(GstBaseTextOverlay * overlay)2591 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2592 {
2593   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2594 
2595   if (overlay->text_buffer) {
2596     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2597         overlay->text_buffer);
2598     gst_buffer_unref (overlay->text_buffer);
2599     overlay->text_buffer = NULL;
2600   }
2601 
2602   /* Let the text task know we used that buffer */
2603   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2604 }
2605 
2606 /* We receive text buffers here. If they are out of segment we just ignore them.
2607    If the buffer is in our segment we keep it internally except if another one
2608    is already waiting here, in that case we wait that it gets kicked out */
2609 static GstFlowReturn
gst_base_text_overlay_text_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)2610 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
2611     GstBuffer * buffer)
2612 {
2613   GstFlowReturn ret = GST_FLOW_OK;
2614   GstBaseTextOverlay *overlay = NULL;
2615   gboolean in_seg = FALSE;
2616   guint64 clip_start = 0, clip_stop = 0;
2617 
2618   overlay = GST_BASE_TEXT_OVERLAY (parent);
2619 
2620   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2621 
2622   if (overlay->text_flushing) {
2623     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2624     ret = GST_FLOW_FLUSHING;
2625     GST_LOG_OBJECT (overlay, "text flushing");
2626     goto beach;
2627   }
2628 
2629   if (overlay->text_eos) {
2630     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2631     ret = GST_FLOW_EOS;
2632     GST_LOG_OBJECT (overlay, "text EOS");
2633     goto beach;
2634   }
2635 
2636   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2637       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2638       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2639       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2640           GST_BUFFER_DURATION (buffer)));
2641 
2642   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2643     GstClockTime stop;
2644 
2645     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2646       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2647     else
2648       stop = GST_CLOCK_TIME_NONE;
2649 
2650     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2651         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2652   } else {
2653     in_seg = TRUE;
2654   }
2655 
2656   if (in_seg) {
2657     /* about to change metadata */
2658     buffer = gst_buffer_make_writable (buffer);
2659     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2660       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2661     if (GST_BUFFER_DURATION_IS_VALID (buffer))
2662       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2663 
2664     /* Wait for the previous buffer to go away */
2665     while (overlay->text_buffer != NULL) {
2666       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2667           GST_DEBUG_PAD_NAME (pad));
2668       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2669       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2670       if (overlay->text_flushing) {
2671         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2672         ret = GST_FLOW_FLUSHING;
2673         goto beach;
2674       }
2675     }
2676 
2677     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2678       overlay->text_segment.position = clip_start;
2679 
2680     overlay->text_buffer = buffer;      /* pass ownership of @buffer */
2681     buffer = NULL;
2682 
2683     /* That's a new text buffer we need to render */
2684     overlay->need_render = TRUE;
2685 
2686     /* in case the video chain is waiting for a text buffer, wake it up */
2687     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2688   }
2689 
2690   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2691 
2692 beach:
2693   if (buffer)
2694     gst_buffer_unref (buffer);
2695 
2696   return ret;
2697 }
2698 
2699 static GstFlowReturn
gst_base_text_overlay_video_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)2700 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2701     GstBuffer * buffer)
2702 {
2703   GstBaseTextOverlayClass *klass;
2704   GstBaseTextOverlay *overlay;
2705   GstFlowReturn ret = GST_FLOW_OK;
2706   gboolean in_seg = FALSE;
2707   guint64 start, stop, clip_start = 0, clip_stop = 0;
2708   gchar *text = NULL;
2709   GstVideoOverlayCompositionMeta *composition_meta;
2710 
2711   overlay = GST_BASE_TEXT_OVERLAY (parent);
2712 
2713   composition_meta = gst_buffer_get_video_overlay_composition_meta (buffer);
2714   if (composition_meta) {
2715     if (overlay->upstream_composition != composition_meta->overlay) {
2716       GST_DEBUG ("GstVideoOverlayCompositionMeta found.");
2717       overlay->upstream_composition = composition_meta->overlay;
2718       overlay->need_render = TRUE;
2719     }
2720   } else if (overlay->upstream_composition != NULL) {
2721     overlay->upstream_composition = NULL;
2722     overlay->need_render = TRUE;
2723   }
2724 
2725   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2726 
2727   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2728     goto missing_timestamp;
2729 
2730   /* ignore buffers that are outside of the current segment */
2731   start = GST_BUFFER_TIMESTAMP (buffer);
2732 
2733   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2734     stop = GST_CLOCK_TIME_NONE;
2735   } else {
2736     stop = start + GST_BUFFER_DURATION (buffer);
2737   }
2738 
2739   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2740       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2741       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2742 
2743   /* segment_clip() will adjust start unconditionally to segment_start if
2744    * no stop time is provided, so handle this ourselves */
2745   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2746     goto out_of_segment;
2747 
2748   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2749       &clip_start, &clip_stop);
2750 
2751   if (!in_seg)
2752     goto out_of_segment;
2753 
2754   /* if the buffer is only partially in the segment, fix up stamps */
2755   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2756     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2757     buffer = gst_buffer_make_writable (buffer);
2758     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2759     if (stop != -1)
2760       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2761   }
2762 
2763   /* now, after we've done the clipping, fix up end time if there's no
2764    * duration (we only use those estimated values internally though, we
2765    * don't want to set bogus values on the buffer itself) */
2766   if (stop == -1) {
2767     if (overlay->info.fps_n && overlay->info.fps_d) {
2768       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2769       stop = start + gst_util_uint64_scale_int (GST_SECOND,
2770           overlay->info.fps_d, overlay->info.fps_n);
2771     } else {
2772       GST_LOG_OBJECT (overlay, "no duration, assuming minimal duration");
2773       stop = start + 1;         /* we need to assume some interval */
2774     }
2775   }
2776 
2777   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2778 
2779 wait_for_text_buf:
2780 
2781   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2782 
2783   if (overlay->video_flushing)
2784     goto flushing;
2785 
2786   if (overlay->video_eos)
2787     goto have_eos;
2788 
2789   if (overlay->silent) {
2790     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2791     ret = gst_pad_push (overlay->srcpad, buffer);
2792 
2793     /* Update position */
2794     overlay->segment.position = clip_start;
2795 
2796     return ret;
2797   }
2798 
2799   /* Text pad not linked, rendering internal text */
2800   if (!overlay->text_linked) {
2801     if (klass->get_text) {
2802       text = klass->get_text (overlay, buffer);
2803     } else {
2804       text = g_strdup (overlay->default_text);
2805     }
2806 
2807     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2808         "text: '%s'", GST_STR_NULL (text));
2809 
2810     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2811 
2812     if (text != NULL && *text != '\0') {
2813       /* Render and push */
2814       gst_base_text_overlay_render_text (overlay, text, -1);
2815       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2816     } else {
2817       /* Invalid or empty string */
2818       ret = gst_pad_push (overlay->srcpad, buffer);
2819     }
2820   } else {
2821     /* Text pad linked, check if we have a text buffer queued */
2822     if (overlay->text_buffer) {
2823       gboolean pop_text = FALSE, valid_text_time = TRUE;
2824       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2825       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2826       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2827       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2828       GstClockTime vid_running_time, vid_running_time_end;
2829 
2830       /* if the text buffer isn't stamped right, pop it off the
2831        * queue and display it for the current video frame only */
2832       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2833           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2834         GST_WARNING_OBJECT (overlay,
2835             "Got text buffer with invalid timestamp or duration");
2836         pop_text = TRUE;
2837         valid_text_time = FALSE;
2838       } else {
2839         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2840         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2841       }
2842 
2843       vid_running_time =
2844           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2845           start);
2846       vid_running_time_end =
2847           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2848           stop);
2849 
2850       /* If timestamp and duration are valid */
2851       if (valid_text_time) {
2852         text_running_time =
2853             gst_segment_to_running_time (&overlay->text_segment,
2854             GST_FORMAT_TIME, text_start);
2855         text_running_time_end =
2856             gst_segment_to_running_time (&overlay->text_segment,
2857             GST_FORMAT_TIME, text_end);
2858       }
2859 
2860       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2861           GST_TIME_ARGS (text_running_time),
2862           GST_TIME_ARGS (text_running_time_end));
2863       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2864           GST_TIME_ARGS (vid_running_time),
2865           GST_TIME_ARGS (vid_running_time_end));
2866 
2867       /* Text too old or in the future */
2868       if (valid_text_time && text_running_time_end <= vid_running_time) {
2869         /* text buffer too old, get rid of it and do nothing  */
2870         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2871         pop_text = FALSE;
2872         gst_base_text_overlay_pop_text (overlay);
2873         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2874         goto wait_for_text_buf;
2875       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2876         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2877         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2878         /* Push the video frame */
2879         ret = gst_pad_push (overlay->srcpad, buffer);
2880       } else {
2881         GstMapInfo map;
2882         gchar *in_text;
2883         gsize in_size;
2884 
2885         gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2886         in_text = (gchar *) map.data;
2887         in_size = map.size;
2888 
2889         if (in_size > 0) {
2890           /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2891            * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2892            * here on purpose, this is something that needs fixing upstream */
2893           if (!g_utf8_validate (in_text, in_size, NULL)) {
2894             const gchar *end = NULL;
2895 
2896             GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2897             in_text = g_strndup (in_text, in_size);
2898             while (!g_utf8_validate (in_text, in_size, &end) && end)
2899               *((gchar *) end) = '*';
2900           }
2901 
2902           /* Get the string */
2903           if (overlay->have_pango_markup) {
2904             text = g_strndup (in_text, in_size);
2905           } else {
2906             text = g_markup_escape_text (in_text, in_size);
2907           }
2908 
2909           if (text != NULL && *text != '\0') {
2910             gint text_len = strlen (text);
2911 
2912             while (text_len > 0 && (text[text_len - 1] == '\n' ||
2913                     text[text_len - 1] == '\r')) {
2914               --text_len;
2915             }
2916             GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2917             gst_base_text_overlay_render_text (overlay, text, text_len);
2918           } else {
2919             GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2920             gst_base_text_overlay_render_text (overlay, " ", 1);
2921           }
2922           if (in_text != (gchar *) map.data)
2923             g_free (in_text);
2924         } else {
2925           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2926           gst_base_text_overlay_render_text (overlay, " ", 1);
2927         }
2928 
2929         gst_buffer_unmap (overlay->text_buffer, &map);
2930 
2931         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2932         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2933 
2934         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2935           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2936           pop_text = TRUE;
2937         }
2938       }
2939       if (pop_text) {
2940         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2941         gst_base_text_overlay_pop_text (overlay);
2942         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2943       }
2944     } else {
2945       gboolean wait_for_text_buf = TRUE;
2946 
2947       if (overlay->text_eos)
2948         wait_for_text_buf = FALSE;
2949 
2950       if (!overlay->wait_text)
2951         wait_for_text_buf = FALSE;
2952 
2953       /* Text pad linked, but no text buffer available - what now? */
2954       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2955         GstClockTime text_start_running_time, text_position_running_time;
2956         GstClockTime vid_running_time;
2957 
2958         vid_running_time =
2959             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2960             GST_BUFFER_TIMESTAMP (buffer));
2961         text_start_running_time =
2962             gst_segment_to_running_time (&overlay->text_segment,
2963             GST_FORMAT_TIME, overlay->text_segment.start);
2964         text_position_running_time =
2965             gst_segment_to_running_time (&overlay->text_segment,
2966             GST_FORMAT_TIME, overlay->text_segment.position);
2967 
2968         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2969                 vid_running_time < text_start_running_time) ||
2970             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2971                 vid_running_time < text_position_running_time)) {
2972           wait_for_text_buf = FALSE;
2973         }
2974       }
2975 
2976       if (wait_for_text_buf) {
2977         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2978         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2979         GST_DEBUG_OBJECT (overlay, "resuming");
2980         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2981         goto wait_for_text_buf;
2982       } else {
2983         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2984         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2985         ret = gst_pad_push (overlay->srcpad, buffer);
2986       }
2987     }
2988   }
2989 
2990   g_free (text);
2991 
2992   /* Update position */
2993   overlay->segment.position = clip_start;
2994 
2995   return ret;
2996 
2997 missing_timestamp:
2998   {
2999     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
3000     gst_buffer_unref (buffer);
3001     return GST_FLOW_OK;
3002   }
3003 
3004 flushing:
3005   {
3006     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
3007     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
3008     gst_buffer_unref (buffer);
3009     return GST_FLOW_FLUSHING;
3010   }
3011 have_eos:
3012   {
3013     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
3014     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
3015     gst_buffer_unref (buffer);
3016     return GST_FLOW_EOS;
3017   }
3018 out_of_segment:
3019   {
3020     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
3021     gst_buffer_unref (buffer);
3022     return GST_FLOW_OK;
3023   }
3024 }
3025 
3026 static GstStateChangeReturn
gst_base_text_overlay_change_state(GstElement * element,GstStateChange transition)3027 gst_base_text_overlay_change_state (GstElement * element,
3028     GstStateChange transition)
3029 {
3030   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
3031   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
3032 
3033   switch (transition) {
3034     case GST_STATE_CHANGE_PAUSED_TO_READY:
3035       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
3036       overlay->text_flushing = TRUE;
3037       overlay->video_flushing = TRUE;
3038       /* pop_text will broadcast on the GCond and thus also make the video
3039        * chain exit if it's waiting for a text buffer */
3040       gst_base_text_overlay_pop_text (overlay);
3041       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
3042       break;
3043     default:
3044       break;
3045   }
3046 
3047   ret = parent_class->change_state (element, transition);
3048   if (ret == GST_STATE_CHANGE_FAILURE)
3049     return ret;
3050 
3051   switch (transition) {
3052     case GST_STATE_CHANGE_READY_TO_PAUSED:
3053       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
3054       overlay->text_flushing = FALSE;
3055       overlay->video_flushing = FALSE;
3056       overlay->video_eos = FALSE;
3057       overlay->text_eos = FALSE;
3058       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
3059       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
3060       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
3061       break;
3062     default:
3063       break;
3064   }
3065 
3066   return ret;
3067 }
3068 
3069 static gboolean
plugin_init(GstPlugin * plugin)3070 plugin_init (GstPlugin * plugin)
3071 {
3072   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
3073           GST_TYPE_TEXT_OVERLAY) ||
3074       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
3075           GST_TYPE_TIME_OVERLAY) ||
3076       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
3077           GST_TYPE_CLOCK_OVERLAY) ||
3078       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
3079           GST_TYPE_TEXT_RENDER)) {
3080     return FALSE;
3081   }
3082 
3083   /*texttestsrc_plugin_init(module, plugin); */
3084 
3085   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
3086 
3087   return TRUE;
3088 }
3089 
3090 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
3091     pango, "Pango-based text rendering and overlay", plugin_init,
3092     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
3093