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