_peer_get_caps() -> _peer_query_caps()
[platform/upstream/gstreamer.git] / ext / pango / gstbasetextoverlay.c
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., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 /**
26  * SECTION:element-textoverlay
27  * @see_also: #GstTextRender, #GstClockOverlay, #GstTimeOverlay, #GstSubParse
28  *
29  * This plugin renders text on top of a video stream. This can be either
30  * static text or text from buffers received on the text sink pad, e.g.
31  * as produced by the subparse element. If the text sink pad is not linked,
32  * the text set via the "text" property will be rendered. If the text sink
33  * pad is linked, text will be rendered as it is received on that pad,
34  * honouring and matching the buffer timestamps of both input streams.
35  *
36  * The text can contain newline characters and text wrapping is enabled by
37  * default.
38  *
39  * <refsect2>
40  * <title>Example launch lines</title>
41  * |[
42  * gst-launch -v videotestsrc ! textoverlay text="Room A" valign=top halign=left ! xvimagesink
43  * ]| Here is a simple pipeline that displays a static text in the top left
44  * corner of the video picture
45  * |[
46  * gst-launch -v filesrc location=subtitles.srt ! subparse ! txt.   videotestsrc ! timeoverlay ! textoverlay name=txt shaded-background=yes ! xvimagesink
47  * ]| Here is another pipeline that displays subtitles from an .srt subtitle
48  * file, centered at the bottom of the picture and with a rectangular shading
49  * around the text in the background:
50  * <para>
51  * If you do not have such a subtitle file, create one looking like this
52  * in a text editor:
53  * |[
54  * 1
55  * 00:00:03,000 --> 00:00:05,000
56  * Hello? (3-5s)
57  *
58  * 2
59  * 00:00:08,000 --> 00:00:13,000
60  * Yes, this is a subtitle. Don&apos;t
61  * you like it? (8-13s)
62  *
63  * 3
64  * 00:00:18,826 --> 00:01:02,886
65  * Uh? What are you talking about?
66  * I don&apos;t understand  (18-62s)
67  * ]|
68  * </para>
69  * </refsect2>
70  */
71
72 /* FIXME: alloc segment as part of instance struct */
73
74 #ifdef HAVE_CONFIG_H
75 #include <config.h>
76 #endif
77
78 #include <gst/video/video.h>
79
80 #include "gstbasetextoverlay.h"
81 #include "gsttextoverlay.h"
82 #include "gsttimeoverlay.h"
83 #include "gstclockoverlay.h"
84 #include "gsttextrender.h"
85 #include <string.h>
86
87 /* FIXME:
88  *  - use proper strides and offset for I420
89  *  - if text is wider than the video picture, it does not get
90  *    clipped properly during blitting (if wrapping is disabled)
91  *  - make 'shading_value' a property (or enum:  light/normal/dark/verydark)?
92  */
93
94 GST_DEBUG_CATEGORY (pango_debug);
95 #define GST_CAT_DEFAULT pango_debug
96
97 #define DEFAULT_PROP_TEXT       ""
98 #define DEFAULT_PROP_SHADING    FALSE
99 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
100 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
101 #define DEFAULT_PROP_VALIGN     "baseline"
102 #define DEFAULT_PROP_HALIGN     "center"
103 #define DEFAULT_PROP_XPAD       25
104 #define DEFAULT_PROP_YPAD       25
105 #define DEFAULT_PROP_DELTAX     0
106 #define DEFAULT_PROP_DELTAY     0
107 #define DEFAULT_PROP_XPOS       0.5
108 #define DEFAULT_PROP_YPOS       0.5
109 #define DEFAULT_PROP_WRAP_MODE  GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
110 #define DEFAULT_PROP_FONT_DESC  ""
111 #define DEFAULT_PROP_SILENT     FALSE
112 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
113 #define DEFAULT_PROP_WAIT_TEXT  TRUE
114 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
115 #define DEFAULT_PROP_VERTICAL_RENDER  FALSE
116 #define DEFAULT_PROP_COLOR      0xffffffff
117 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
118
119 /* make a property of me */
120 #define DEFAULT_SHADING_VALUE    -80
121
122 #define MINIMUM_OUTLINE_OFFSET 1.0
123 #define DEFAULT_SCALE_BASIS    640
124
125 #define COMP_Y(ret, r, g, b) \
126 { \
127    ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
128    ret = CLAMP (ret, 0, 255); \
129 }
130
131 #define COMP_U(ret, r, g, b) \
132 { \
133    ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
134    ret = CLAMP (ret, 0, 255); \
135 }
136
137 #define COMP_V(ret, r, g, b) \
138 { \
139    ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
140    ret = CLAMP (ret, 0, 255); \
141 }
142
143 #define BLEND(ret, alpha, v0, v1) \
144 { \
145         ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
146 }
147
148 #define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew)     \
149 { \
150     gint _tmp; \
151     _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \
152     ret = CLAMP (_tmp, 0, 255); \
153 }
154
155 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
156 # define CAIRO_ARGB_A 3
157 # define CAIRO_ARGB_R 2
158 # define CAIRO_ARGB_G 1
159 # define CAIRO_ARGB_B 0
160 #else
161 # define CAIRO_ARGB_A 0
162 # define CAIRO_ARGB_R 1
163 # define CAIRO_ARGB_G 2
164 # define CAIRO_ARGB_B 3
165 #endif
166
167 enum
168 {
169   PROP_0,
170   PROP_TEXT,
171   PROP_SHADING,
172   PROP_VALIGN,                  /* deprecated */
173   PROP_HALIGN,                  /* deprecated */
174   PROP_HALIGNMENT,
175   PROP_VALIGNMENT,
176   PROP_XPAD,
177   PROP_YPAD,
178   PROP_DELTAX,
179   PROP_DELTAY,
180   PROP_XPOS,
181   PROP_YPOS,
182   PROP_WRAP_MODE,
183   PROP_FONT_DESC,
184   PROP_SILENT,
185   PROP_LINE_ALIGNMENT,
186   PROP_WAIT_TEXT,
187   PROP_AUTO_ADJUST_SIZE,
188   PROP_VERTICAL_RENDER,
189   PROP_COLOR,
190   PROP_SHADOW,
191   PROP_OUTLINE_COLOR,
192   PROP_LAST
193 };
194
195 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, AYUV, I420, UYVY, NV12, NV21 } "
196
197 static GstStaticPadTemplate src_template_factory =
198 GST_STATIC_PAD_TEMPLATE ("src",
199     GST_PAD_SRC,
200     GST_PAD_ALWAYS,
201     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
202     );
203
204 static GstStaticPadTemplate video_sink_template_factory =
205 GST_STATIC_PAD_TEMPLATE ("video_sink",
206     GST_PAD_SINK,
207     GST_PAD_ALWAYS,
208     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
209     );
210
211 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
212 static GType
213 gst_base_text_overlay_valign_get_type (void)
214 {
215   static GType base_text_overlay_valign_type = 0;
216   static const GEnumValue base_text_overlay_valign[] = {
217     {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
218     {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
219     {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
220     {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
221     {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
222     {0, NULL, NULL},
223   };
224
225   if (!base_text_overlay_valign_type) {
226     base_text_overlay_valign_type =
227         g_enum_register_static ("GstBaseTextOverlayVAlign",
228         base_text_overlay_valign);
229   }
230   return base_text_overlay_valign_type;
231 }
232
233 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
234 static GType
235 gst_base_text_overlay_halign_get_type (void)
236 {
237   static GType base_text_overlay_halign_type = 0;
238   static const GEnumValue base_text_overlay_halign[] = {
239     {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
240     {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
241     {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
242     {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
243     {0, NULL, NULL},
244   };
245
246   if (!base_text_overlay_halign_type) {
247     base_text_overlay_halign_type =
248         g_enum_register_static ("GstBaseTextOverlayHAlign",
249         base_text_overlay_halign);
250   }
251   return base_text_overlay_halign_type;
252 }
253
254
255 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
256 static GType
257 gst_base_text_overlay_wrap_mode_get_type (void)
258 {
259   static GType base_text_overlay_wrap_mode_type = 0;
260   static const GEnumValue base_text_overlay_wrap_mode[] = {
261     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
262     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
263     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
264     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
265     {0, NULL, NULL},
266   };
267
268   if (!base_text_overlay_wrap_mode_type) {
269     base_text_overlay_wrap_mode_type =
270         g_enum_register_static ("GstBaseTextOverlayWrapMode",
271         base_text_overlay_wrap_mode);
272   }
273   return base_text_overlay_wrap_mode_type;
274 }
275
276 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
277 static GType
278 gst_base_text_overlay_line_align_get_type (void)
279 {
280   static GType base_text_overlay_line_align_type = 0;
281   static const GEnumValue base_text_overlay_line_align[] = {
282     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
283     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
284     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
285     {0, NULL, NULL}
286   };
287
288   if (!base_text_overlay_line_align_type) {
289     base_text_overlay_line_align_type =
290         g_enum_register_static ("GstBaseTextOverlayLineAlign",
291         base_text_overlay_line_align);
292   }
293   return base_text_overlay_line_align_type;
294 }
295
296 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (((GstBaseTextOverlay *)ov)->cond)
297 #define GST_BASE_TEXT_OVERLAY_WAIT(ov)     (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_OBJECT_GET_LOCK (ov)))
298 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov)   (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
299 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
300
301 static GstElementClass *parent_class = NULL;
302 static void gst_base_text_overlay_base_init (gpointer g_class);
303 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
304 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
305     GstBaseTextOverlayClass * klass);
306
307 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
308     element, GstStateChange transition);
309
310 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad, GstCaps * filter);
311 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
312     GstCaps * caps);
313 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
314     GstCaps * caps);
315 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
316     GstEvent * event);
317 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
318     GstQuery * query);
319
320 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
321     GstEvent * event);
322 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
323     GstQuery * query);
324 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
325     GstBuffer * buffer);
326
327 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
328     GstEvent * event);
329 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
330     GstBuffer * buffer);
331 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
332     GstPad * peer);
333 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad);
334 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
335 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
336     overlay);
337
338 static void gst_base_text_overlay_finalize (GObject * object);
339 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
340     const GValue * value, GParamSpec * pspec);
341 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
342     GValue * value, GParamSpec * pspec);
343 static void
344 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
345     PangoFontDescription * desc);
346
347 GType
348 gst_base_text_overlay_get_type (void)
349 {
350   static GType type = 0;
351
352   if (g_once_init_enter ((gsize *) & type)) {
353     static const GTypeInfo info = {
354       sizeof (GstBaseTextOverlayClass),
355       (GBaseInitFunc) gst_base_text_overlay_base_init,
356       NULL,
357       (GClassInitFunc) gst_base_text_overlay_class_init,
358       NULL,
359       NULL,
360       sizeof (GstBaseTextOverlay),
361       0,
362       (GInstanceInitFunc) gst_base_text_overlay_init,
363     };
364
365     g_once_init_leave ((gsize *) & type,
366         g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
367             0));
368   }
369
370   return type;
371 }
372
373 static gchar *
374 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
375     GstBuffer * video_frame)
376 {
377   return g_strdup (overlay->default_text);
378 }
379
380 static void
381 gst_base_text_overlay_base_init (gpointer g_class)
382 {
383   GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
384   PangoFontMap *fontmap;
385
386   /* Only lock for the subclasses here, the base class
387    * doesn't have this mutex yet and it's not necessary
388    * here */
389   if (klass->pango_lock)
390     g_mutex_lock (klass->pango_lock);
391   fontmap = pango_cairo_font_map_get_default ();
392   klass->pango_context =
393       pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (fontmap));
394   if (klass->pango_lock)
395     g_mutex_unlock (klass->pango_lock);
396 }
397
398 static void
399 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
400 {
401   GObjectClass *gobject_class;
402   GstElementClass *gstelement_class;
403
404   gobject_class = (GObjectClass *) klass;
405   gstelement_class = (GstElementClass *) klass;
406
407   parent_class = g_type_class_peek_parent (klass);
408
409   gobject_class->finalize = gst_base_text_overlay_finalize;
410   gobject_class->set_property = gst_base_text_overlay_set_property;
411   gobject_class->get_property = gst_base_text_overlay_get_property;
412
413   gst_element_class_add_pad_template (gstelement_class,
414       gst_static_pad_template_get (&src_template_factory));
415   gst_element_class_add_pad_template (gstelement_class,
416       gst_static_pad_template_get (&video_sink_template_factory));
417
418   gstelement_class->change_state =
419       GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
420
421   klass->pango_lock = g_mutex_new ();
422
423   klass->get_text = gst_base_text_overlay_get_text;
424
425   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
426       g_param_spec_string ("text", "text",
427           "Text to be display.", DEFAULT_PROP_TEXT,
428           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
429   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
430       g_param_spec_boolean ("shaded-background", "shaded background",
431           "Whether to shade the background under the text area",
432           DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
433   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
434       g_param_spec_enum ("valignment", "vertical alignment",
435           "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
436           DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
437   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
438       g_param_spec_enum ("halignment", "horizontal alignment",
439           "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
440           DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
441   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGN,
442       g_param_spec_string ("valign", "vertical alignment",
443           "Vertical alignment of the text (deprecated; use valignment)",
444           DEFAULT_PROP_VALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
445   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGN,
446       g_param_spec_string ("halign", "horizontal alignment",
447           "Horizontal alignment of the text (deprecated; use halignment)",
448           DEFAULT_PROP_HALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
449   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
450       g_param_spec_int ("xpad", "horizontal paddding",
451           "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
452           DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
453   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
454       g_param_spec_int ("ypad", "vertical padding",
455           "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
456           DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
457   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
458       g_param_spec_int ("deltax", "X position modifier",
459           "Shift X position to the left or to the right. Unit is pixels.",
460           G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
461           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
462   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
463       g_param_spec_int ("deltay", "Y position modifier",
464           "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
465           DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
466   /**
467    * GstBaseTextOverlay:xpos
468    *
469    * Horizontal position of the rendered text when using positioned alignment.
470    *
471    * Since: 0.10.31
472    **/
473   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
474       g_param_spec_double ("xpos", "horizontal position",
475           "Horizontal position when using position alignment", 0, 1.0,
476           DEFAULT_PROP_XPOS,
477           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
478   /**
479    * GstBaseTextOverlay:ypos
480    *
481    * Vertical position of the rendered text when using positioned alignment.
482    *
483    * Since: 0.10.31
484    **/
485   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
486       g_param_spec_double ("ypos", "vertical position",
487           "Vertical position when using position alignment", 0, 1.0,
488           DEFAULT_PROP_YPOS,
489           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
490   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
491       g_param_spec_enum ("wrap-mode", "wrap mode",
492           "Whether to wrap the text and if so how.",
493           GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
494           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
495   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
496       g_param_spec_string ("font-desc", "font description",
497           "Pango font description of font to be used for rendering. "
498           "See documentation of pango_font_description_from_string "
499           "for syntax.", DEFAULT_PROP_FONT_DESC,
500           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
501   /**
502    * GstBaseTextOverlay:color
503    *
504    * Color of the rendered text.
505    *
506    * Since: 0.10.31
507    **/
508   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
509       g_param_spec_uint ("color", "Color",
510           "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
511           DEFAULT_PROP_COLOR,
512           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
513   /**
514    * GstTextOverlay:outline-color
515    *
516    * Color of the outline of the rendered text.
517    *
518    * Since: 0.10.35
519    **/
520   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
521       g_param_spec_uint ("outline-color", "Text Outline Color",
522           "Color to use for outline the text (big-endian ARGB).", 0,
523           G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
524           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
525
526   /**
527    * GstBaseTextOverlay:line-alignment
528    *
529    * Alignment of text lines relative to each other (for multi-line text)
530    *
531    * Since: 0.10.15
532    **/
533   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
534       g_param_spec_enum ("line-alignment", "line alignment",
535           "Alignment of text lines relative to each other.",
536           GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
537           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
538   /**
539    * GstBaseTextOverlay:silent
540    *
541    * If set, no text is rendered. Useful to switch off text rendering
542    * temporarily without removing the textoverlay element from the pipeline.
543    *
544    * Since: 0.10.15
545    **/
546   /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
547   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
548       g_param_spec_boolean ("silent", "silent",
549           "Whether to render the text string",
550           DEFAULT_PROP_SILENT,
551           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
552   /**
553    * GstBaseTextOverlay:wait-text
554    *
555    * If set, the video will block until a subtitle is received on the text pad.
556    * If video and subtitles are sent in sync, like from the same demuxer, this
557    * property should be set.
558    *
559    * Since: 0.10.20
560    **/
561   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
562       g_param_spec_boolean ("wait-text", "Wait Text",
563           "Whether to wait for subtitles",
564           DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
565
566   g_object_class_install_property (G_OBJECT_CLASS (klass),
567       PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
568           "Automatically adjust font size to screen-size.",
569           DEFAULT_PROP_AUTO_ADJUST_SIZE,
570           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
571
572   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
573       g_param_spec_boolean ("vertical-render", "vertical render",
574           "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
575           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
576 }
577
578 static void
579 gst_base_text_overlay_finalize (GObject * object)
580 {
581   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
582
583   g_free (overlay->default_text);
584
585   if (overlay->text_image) {
586     g_free (overlay->text_image);
587     overlay->text_image = NULL;
588   }
589
590   if (overlay->layout) {
591     g_object_unref (overlay->layout);
592     overlay->layout = NULL;
593   }
594
595   if (overlay->text_buffer) {
596     gst_buffer_unref (overlay->text_buffer);
597     overlay->text_buffer = NULL;
598   }
599
600   if (overlay->cond) {
601     g_cond_free (overlay->cond);
602     overlay->cond = NULL;
603   }
604
605   G_OBJECT_CLASS (parent_class)->finalize (object);
606 }
607
608 static void
609 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
610     GstBaseTextOverlayClass * klass)
611 {
612   GstPadTemplate *template;
613   PangoFontDescription *desc;
614
615   /* video sink */
616   template = gst_static_pad_template_get (&video_sink_template_factory);
617   overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
618   gst_object_unref (template);
619   gst_pad_set_event_function (overlay->video_sinkpad,
620       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
621   gst_pad_set_chain_function (overlay->video_sinkpad,
622       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
623   gst_pad_set_query_function (overlay->video_sinkpad,
624       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
625   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
626
627   template =
628       gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
629       "text_sink");
630   if (template) {
631     /* text sink */
632     overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
633     gst_object_unref (template);
634
635     gst_pad_set_event_function (overlay->text_sinkpad,
636         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
637     gst_pad_set_chain_function (overlay->text_sinkpad,
638         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
639     gst_pad_set_link_function (overlay->text_sinkpad,
640         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
641     gst_pad_set_unlink_function (overlay->text_sinkpad,
642         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
643     gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
644   }
645
646   /* (video) source */
647   template = gst_static_pad_template_get (&src_template_factory);
648   overlay->srcpad = gst_pad_new_from_template (template, "src");
649   gst_object_unref (template);
650   gst_pad_set_event_function (overlay->srcpad,
651       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
652   gst_pad_set_query_function (overlay->srcpad,
653       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
654   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
655
656   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
657   overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
658   overlay->layout =
659       pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
660       (overlay)->pango_context);
661   desc =
662       pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
663       (overlay)->pango_context);
664   gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
665
666   overlay->color = DEFAULT_PROP_COLOR;
667   overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
668   overlay->halign = DEFAULT_PROP_HALIGNMENT;
669   overlay->valign = DEFAULT_PROP_VALIGNMENT;
670   overlay->xpad = DEFAULT_PROP_XPAD;
671   overlay->ypad = DEFAULT_PROP_YPAD;
672   overlay->deltax = DEFAULT_PROP_DELTAX;
673   overlay->deltay = DEFAULT_PROP_DELTAY;
674   overlay->xpos = DEFAULT_PROP_XPOS;
675   overlay->ypos = DEFAULT_PROP_YPOS;
676
677   overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
678
679   overlay->want_shading = DEFAULT_PROP_SHADING;
680   overlay->shading_value = DEFAULT_SHADING_VALUE;
681   overlay->silent = DEFAULT_PROP_SILENT;
682   overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
683   overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
684
685   overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
686   overlay->need_render = TRUE;
687   overlay->text_image = NULL;
688   overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
689   gst_base_text_overlay_update_render_mode (overlay);
690
691   overlay->text_buffer = NULL;
692   overlay->text_linked = FALSE;
693   overlay->cond = g_cond_new ();
694   gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
695   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
696 }
697
698 static void
699 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
700 {
701   if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
702     GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
703     pango_layout_set_width (overlay->layout, -1);
704   } else {
705     int width;
706
707     if (overlay->auto_adjust_size) {
708       width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
709       if (overlay->use_vertical_render) {
710         width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
711       }
712     } else {
713       width =
714           (overlay->use_vertical_render ? overlay->height : overlay->width) *
715           PANGO_SCALE;
716     }
717
718     GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
719     GST_DEBUG_OBJECT (overlay, "Set wrap mode    %d", overlay->wrap_mode);
720     pango_layout_set_width (overlay->layout, width);
721     pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
722   }
723 }
724
725 static void
726 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
727 {
728   PangoMatrix matrix = PANGO_MATRIX_INIT;
729   PangoContext *context = pango_layout_get_context (overlay->layout);
730
731   if (overlay->use_vertical_render) {
732     pango_matrix_rotate (&matrix, -90);
733     pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
734     pango_context_set_matrix (context, &matrix);
735     pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
736   } else {
737     pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
738     pango_context_set_matrix (context, &matrix);
739     pango_layout_set_alignment (overlay->layout, overlay->line_align);
740   }
741 }
742
743 static gboolean
744 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
745 {
746   GstStructure *structure;
747
748   structure = gst_caps_get_structure (caps, 0);
749   overlay->have_pango_markup =
750       gst_structure_has_name (structure, "text/x-pango-markup");
751
752   return TRUE;
753 }
754
755 /* FIXME: upstream nego (e.g. when the video window is resized) */
756
757 static gboolean
758 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
759 {
760   GstVideoInfo info;
761   gboolean ret = FALSE;
762
763   if (!gst_video_info_from_caps (&info, caps))
764     goto invalid_caps;
765
766   overlay->info = info;
767   overlay->format = GST_VIDEO_INFO_FORMAT (&info);
768   overlay->width = GST_VIDEO_INFO_WIDTH (&info);
769   overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
770
771   ret = gst_pad_push_event (overlay->srcpad, gst_event_new_caps (caps));
772
773   if (ret) {
774     GST_OBJECT_LOCK (overlay);
775     g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
776     gst_base_text_overlay_update_wrap_mode (overlay);
777     g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
778     GST_OBJECT_UNLOCK (overlay);
779   }
780
781   return ret;
782
783   /* ERRORS */
784 invalid_caps:
785   {
786     GST_DEBUG_OBJECT (overlay, "could not parse caps");
787     return FALSE;
788   }
789 }
790
791 static void
792 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
793     const GValue * value, GParamSpec * pspec)
794 {
795   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
796
797   GST_OBJECT_LOCK (overlay);
798   switch (prop_id) {
799     case PROP_TEXT:
800       g_free (overlay->default_text);
801       overlay->default_text = g_value_dup_string (value);
802       overlay->need_render = TRUE;
803       break;
804     case PROP_SHADING:
805       overlay->want_shading = g_value_get_boolean (value);
806       break;
807     case PROP_XPAD:
808       overlay->xpad = g_value_get_int (value);
809       break;
810     case PROP_YPAD:
811       overlay->ypad = g_value_get_int (value);
812       break;
813     case PROP_DELTAX:
814       overlay->deltax = g_value_get_int (value);
815       break;
816     case PROP_DELTAY:
817       overlay->deltay = g_value_get_int (value);
818       break;
819     case PROP_XPOS:
820       overlay->xpos = g_value_get_double (value);
821       break;
822     case PROP_YPOS:
823       overlay->ypos = g_value_get_double (value);
824       break;
825     case PROP_HALIGN:{
826       const gchar *s = g_value_get_string (value);
827
828       if (s && g_ascii_strcasecmp (s, "left") == 0)
829         overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_LEFT;
830       else if (s && g_ascii_strcasecmp (s, "center") == 0)
831         overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_CENTER;
832       else if (s && g_ascii_strcasecmp (s, "right") == 0)
833         overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
834       else
835         g_warning ("Invalid value '%s' for textoverlay property 'halign'",
836             GST_STR_NULL (s));
837       break;
838     }
839     case PROP_VALIGN:{
840       const gchar *s = g_value_get_string (value);
841
842       if (s && g_ascii_strcasecmp (s, "baseline") == 0)
843         overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE;
844       else if (s && g_ascii_strcasecmp (s, "bottom") == 0)
845         overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM;
846       else if (s && g_ascii_strcasecmp (s, "top") == 0)
847         overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
848       else
849         g_warning ("Invalid value '%s' for textoverlay property 'valign'",
850             GST_STR_NULL (s));
851       break;
852     }
853     case PROP_VALIGNMENT:
854       overlay->valign = g_value_get_enum (value);
855       break;
856     case PROP_HALIGNMENT:
857       overlay->halign = g_value_get_enum (value);
858       break;
859     case PROP_WRAP_MODE:
860       overlay->wrap_mode = g_value_get_enum (value);
861       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
862       gst_base_text_overlay_update_wrap_mode (overlay);
863       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
864       break;
865     case PROP_FONT_DESC:
866     {
867       PangoFontDescription *desc;
868       const gchar *fontdesc_str;
869
870       fontdesc_str = g_value_get_string (value);
871       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
872       desc = pango_font_description_from_string (fontdesc_str);
873       if (desc) {
874         GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
875         pango_layout_set_font_description (overlay->layout, desc);
876         gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
877         pango_font_description_free (desc);
878       } else {
879         GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
880             fontdesc_str);
881       }
882       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
883       break;
884     }
885     case PROP_COLOR:
886       overlay->color = g_value_get_uint (value);
887       break;
888     case PROP_OUTLINE_COLOR:
889       overlay->outline_color = g_value_get_uint (value);
890       break;
891     case PROP_SILENT:
892       overlay->silent = g_value_get_boolean (value);
893       break;
894     case PROP_LINE_ALIGNMENT:
895       overlay->line_align = g_value_get_enum (value);
896       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
897       pango_layout_set_alignment (overlay->layout,
898           (PangoAlignment) overlay->line_align);
899       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
900       break;
901     case PROP_WAIT_TEXT:
902       overlay->wait_text = g_value_get_boolean (value);
903       break;
904     case PROP_AUTO_ADJUST_SIZE:
905       overlay->auto_adjust_size = g_value_get_boolean (value);
906       overlay->need_render = TRUE;
907       break;
908     case PROP_VERTICAL_RENDER:
909       overlay->use_vertical_render = g_value_get_boolean (value);
910       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
911       gst_base_text_overlay_update_render_mode (overlay);
912       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
913       overlay->need_render = TRUE;
914       break;
915     default:
916       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
917       break;
918   }
919
920   overlay->need_render = TRUE;
921   GST_OBJECT_UNLOCK (overlay);
922 }
923
924 static void
925 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
926     GValue * value, GParamSpec * pspec)
927 {
928   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
929
930   GST_OBJECT_LOCK (overlay);
931   switch (prop_id) {
932     case PROP_TEXT:
933       g_value_set_string (value, overlay->default_text);
934       break;
935     case PROP_SHADING:
936       g_value_set_boolean (value, overlay->want_shading);
937       break;
938     case PROP_XPAD:
939       g_value_set_int (value, overlay->xpad);
940       break;
941     case PROP_YPAD:
942       g_value_set_int (value, overlay->ypad);
943       break;
944     case PROP_DELTAX:
945       g_value_set_int (value, overlay->deltax);
946       break;
947     case PROP_DELTAY:
948       g_value_set_int (value, overlay->deltay);
949       break;
950     case PROP_XPOS:
951       g_value_set_double (value, overlay->xpos);
952       break;
953     case PROP_YPOS:
954       g_value_set_double (value, overlay->ypos);
955       break;
956     case PROP_VALIGNMENT:
957       g_value_set_enum (value, overlay->valign);
958       break;
959     case PROP_HALIGNMENT:
960       g_value_set_enum (value, overlay->halign);
961       break;
962     case PROP_WRAP_MODE:
963       g_value_set_enum (value, overlay->wrap_mode);
964       break;
965     case PROP_SILENT:
966       g_value_set_boolean (value, overlay->silent);
967       break;
968     case PROP_LINE_ALIGNMENT:
969       g_value_set_enum (value, overlay->line_align);
970       break;
971     case PROP_WAIT_TEXT:
972       g_value_set_boolean (value, overlay->wait_text);
973       break;
974     case PROP_AUTO_ADJUST_SIZE:
975       g_value_set_boolean (value, overlay->auto_adjust_size);
976       break;
977     case PROP_VERTICAL_RENDER:
978       g_value_set_boolean (value, overlay->use_vertical_render);
979       break;
980     case PROP_COLOR:
981       g_value_set_uint (value, overlay->color);
982       break;
983     case PROP_OUTLINE_COLOR:
984       g_value_set_uint (value, overlay->outline_color);
985       break;
986     default:
987       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
988       break;
989   }
990
991   overlay->need_render = TRUE;
992   GST_OBJECT_UNLOCK (overlay);
993 }
994
995 static gboolean
996 gst_base_text_overlay_src_query (GstPad * pad, GstQuery * query)
997 {
998   gboolean ret = FALSE;
999   GstBaseTextOverlay *overlay = NULL;
1000
1001   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1002   if (G_UNLIKELY (!overlay))
1003     return FALSE;
1004
1005   switch (GST_QUERY_TYPE (query)) {
1006     case GST_QUERY_CAPS:
1007     {
1008       GstCaps *filter, *caps;
1009
1010       gst_query_parse_caps (query, &filter);
1011       caps = gst_base_text_overlay_getcaps (pad, filter);
1012       gst_query_set_caps_result (query, caps);
1013       gst_caps_unref (caps);
1014       ret = TRUE;
1015       break;
1016     }
1017     default:
1018       ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1019       break;
1020   }
1021
1022   gst_object_unref (overlay);
1023
1024   return ret;
1025 }
1026
1027 static gboolean
1028 gst_base_text_overlay_src_event (GstPad * pad, GstEvent * event)
1029 {
1030   gboolean ret = FALSE;
1031   GstBaseTextOverlay *overlay = NULL;
1032
1033   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1034   if (G_UNLIKELY (!overlay)) {
1035     gst_event_unref (event);
1036     return FALSE;
1037   }
1038
1039   switch (GST_EVENT_TYPE (event)) {
1040     case GST_EVENT_SEEK:{
1041       GstSeekFlags flags;
1042
1043       /* We don't handle seek if we have not text pad */
1044       if (!overlay->text_linked) {
1045         GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1046         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1047         goto beach;
1048       }
1049
1050       GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1051
1052       gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1053
1054       /* Flush downstream, only for flushing seek */
1055       if (flags & GST_SEEK_FLAG_FLUSH)
1056         gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1057
1058       /* Mark ourself as flushing, unblock chains */
1059       GST_OBJECT_LOCK (overlay);
1060       overlay->video_flushing = TRUE;
1061       overlay->text_flushing = TRUE;
1062       gst_base_text_overlay_pop_text (overlay);
1063       GST_OBJECT_UNLOCK (overlay);
1064
1065       /* Seek on each sink pad */
1066       gst_event_ref (event);
1067       ret = gst_pad_push_event (overlay->video_sinkpad, event);
1068       if (ret) {
1069         ret = gst_pad_push_event (overlay->text_sinkpad, event);
1070       } else {
1071         gst_event_unref (event);
1072       }
1073       break;
1074     }
1075     default:
1076       if (overlay->text_linked) {
1077         gst_event_ref (event);
1078         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1079         gst_pad_push_event (overlay->text_sinkpad, event);
1080       } else {
1081         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1082       }
1083       break;
1084   }
1085
1086 beach:
1087   gst_object_unref (overlay);
1088
1089   return ret;
1090 }
1091
1092 static GstCaps *
1093 gst_base_text_overlay_getcaps (GstPad * pad, GstCaps * filter)
1094 {
1095   GstBaseTextOverlay *overlay;
1096   GstPad *otherpad;
1097   GstCaps *caps;
1098
1099   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1100   if (G_UNLIKELY (!overlay))
1101     return gst_caps_copy (gst_pad_get_pad_template_caps (pad));
1102
1103   if (pad == overlay->srcpad)
1104     otherpad = overlay->video_sinkpad;
1105   else
1106     otherpad = overlay->srcpad;
1107
1108   /* we can do what the peer can */
1109   caps = gst_pad_peer_query_caps (otherpad, filter);
1110   if (caps) {
1111     GstCaps *temp, *templ;
1112
1113     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, caps);
1114
1115     /* filtered against our padtemplate */
1116     templ = gst_pad_get_pad_template_caps (otherpad);
1117     GST_DEBUG_OBJECT (pad, "our template  %" GST_PTR_FORMAT, templ);
1118     temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1119     GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1120     gst_caps_unref (caps);
1121     gst_caps_unref (templ);
1122     /* this is what we can do */
1123     caps = temp;
1124   } else {
1125     /* no peer, our padtemplate is enough then */
1126     caps = gst_pad_get_pad_template_caps (pad);
1127     if (filter) {
1128       GstCaps *intersection;
1129
1130       intersection =
1131           gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1132       gst_caps_unref (caps);
1133       caps = intersection;
1134     }
1135   }
1136
1137   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
1138
1139   gst_object_unref (overlay);
1140
1141   return caps;
1142 }
1143
1144 static void
1145 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1146     PangoFontDescription * desc)
1147 {
1148   gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1149   overlay->shadow_offset = (double) (font_size) / 13.0;
1150   overlay->outline_offset = (double) (font_size) / 15.0;
1151   if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1152     overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1153 }
1154
1155 #define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \
1156   b = (a > 0) ? MIN ((b * 255 + a / 2) / a, 255) : 0; \
1157   g = (a > 0) ? MIN ((g * 255 + a / 2) / a, 255) : 0; \
1158   r = (a > 0) ? MIN ((r * 255 + a / 2) / a, 255) : 0; \
1159 } G_STMT_END
1160
1161 static inline void
1162 gst_base_text_overlay_blit_1 (GstBaseTextOverlay * overlay, guchar * dest,
1163     gint xpos, gint ypos, guchar * text_image, guint dest_stride)
1164 {
1165   gint i, j = 0;
1166   gint x, y;
1167   guchar r, g, b, a;
1168   guchar *pimage;
1169   guchar *py;
1170   gint width = overlay->image_width;
1171   gint height = overlay->image_height;
1172
1173   if (xpos < 0) {
1174     xpos = 0;
1175   }
1176
1177   if (xpos + width > overlay->width) {
1178     width = overlay->width - xpos;
1179   }
1180
1181   if (ypos + height > overlay->height) {
1182     height = overlay->height - ypos;
1183   }
1184
1185   dest += (ypos / 1) * dest_stride;
1186
1187   for (i = 0; i < height; i++) {
1188     pimage = text_image + 4 * (i * overlay->image_width);
1189     py = dest + i * dest_stride + xpos;
1190     for (j = 0; j < width; j++) {
1191       b = pimage[CAIRO_ARGB_B];
1192       g = pimage[CAIRO_ARGB_G];
1193       r = pimage[CAIRO_ARGB_R];
1194       a = pimage[CAIRO_ARGB_A];
1195       CAIRO_UNPREMULTIPLY (a, r, g, b);
1196
1197       pimage += 4;
1198       if (a == 0) {
1199         py++;
1200         continue;
1201       }
1202       COMP_Y (y, r, g, b);
1203       x = *py;
1204       BLEND (*py++, a, y, x);
1205     }
1206   }
1207 }
1208
1209 static inline void
1210 gst_base_text_overlay_blit_sub2x2cbcr (GstBaseTextOverlay * overlay,
1211     guchar * destcb, guchar * destcr, gint xpos, gint ypos, guchar * text_image,
1212     guint destcb_stride, guint destcr_stride, guint pix_stride)
1213 {
1214   gint i, j;
1215   gint x, cb, cr;
1216   gushort r, g, b, a;
1217   gushort r1, g1, b1, a1;
1218   guchar *pimage1, *pimage2;
1219   guchar *pcb, *pcr;
1220   gint width = overlay->image_width - 2;
1221   gint height = overlay->image_height - 2;
1222
1223   xpos *= pix_stride;
1224
1225   if (xpos < 0) {
1226     xpos = 0;
1227   }
1228
1229   if (xpos + width > overlay->width) {
1230     width = overlay->width - xpos;
1231   }
1232
1233   if (ypos + height > overlay->height) {
1234     height = overlay->height - ypos;
1235   }
1236
1237   destcb += (ypos / 2) * destcb_stride;
1238   destcr += (ypos / 2) * destcr_stride;
1239
1240   for (i = 0; i < height; i += 2) {
1241     pimage1 = text_image + 4 * (i * overlay->image_width);
1242     pimage2 = pimage1 + 4 * overlay->image_width;
1243     pcb = destcb + (i / 2) * destcb_stride + xpos / 2;
1244     pcr = destcr + (i / 2) * destcr_stride + xpos / 2;
1245     for (j = 0; j < width; j += 2) {
1246       b = pimage1[CAIRO_ARGB_B];
1247       g = pimage1[CAIRO_ARGB_G];
1248       r = pimage1[CAIRO_ARGB_R];
1249       a = pimage1[CAIRO_ARGB_A];
1250       CAIRO_UNPREMULTIPLY (a, r, g, b);
1251       pimage1 += 4;
1252
1253       b1 = pimage1[CAIRO_ARGB_B];
1254       g1 = pimage1[CAIRO_ARGB_G];
1255       r1 = pimage1[CAIRO_ARGB_R];
1256       a1 = pimage1[CAIRO_ARGB_A];
1257       CAIRO_UNPREMULTIPLY (a1, r1, g1, b1);
1258       b += b1;
1259       g += g1;
1260       r += r1;
1261       a += a1;
1262       pimage1 += 4;
1263
1264       b1 = pimage2[CAIRO_ARGB_B];
1265       g1 = pimage2[CAIRO_ARGB_G];
1266       r1 = pimage2[CAIRO_ARGB_R];
1267       a1 = pimage2[CAIRO_ARGB_A];
1268       CAIRO_UNPREMULTIPLY (a1, r1, g1, b1);
1269       b += b1;
1270       g += g1;
1271       r += r1;
1272       a += a1;
1273       pimage2 += 4;
1274
1275       /* + 2 for rounding */
1276       b1 = pimage2[CAIRO_ARGB_B];
1277       g1 = pimage2[CAIRO_ARGB_G];
1278       r1 = pimage2[CAIRO_ARGB_R];
1279       a1 = pimage2[CAIRO_ARGB_A];
1280       CAIRO_UNPREMULTIPLY (a1, r1, g1, b1);
1281       b += b1 + 2;
1282       g += g1 + 2;
1283       r += r1 + 2;
1284       a += a1 + 2;
1285       pimage2 += 4;
1286
1287       b /= 4;
1288       g /= 4;
1289       r /= 4;
1290       a /= 4;
1291
1292       if (a == 0) {
1293         pcb += pix_stride;
1294         pcr += pix_stride;
1295         continue;
1296       }
1297       COMP_U (cb, r, g, b);
1298       COMP_V (cr, r, g, b);
1299
1300       x = *pcb;
1301       BLEND (*pcb, a, cb, x);
1302       x = *pcr;
1303       BLEND (*pcr, a, cr, x);
1304
1305       pcb += pix_stride;
1306       pcr += pix_stride;
1307     }
1308   }
1309 }
1310
1311 static void
1312 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1313     const gchar * string, gint textlen)
1314 {
1315   cairo_t *cr;
1316   cairo_surface_t *surface;
1317   PangoRectangle ink_rect, logical_rect;
1318   cairo_matrix_t cairo_matrix;
1319   int width, height;
1320   double scalef = 1.0;
1321   double a, r, g, b;
1322
1323   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1324
1325   if (overlay->auto_adjust_size) {
1326     /* 640 pixel is default */
1327     scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1328   }
1329   pango_layout_set_width (overlay->layout, -1);
1330   /* set text on pango layout */
1331   pango_layout_set_markup (overlay->layout, string, textlen);
1332
1333   /* get subtitle image size */
1334   pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1335
1336   width = (logical_rect.width + overlay->shadow_offset) * scalef;
1337
1338   if (width + overlay->deltax >
1339       (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1340     /*
1341      * subtitle image width is larger then overlay width
1342      * so rearrange overlay wrap mode.
1343      */
1344     gst_base_text_overlay_update_wrap_mode (overlay);
1345     pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1346     width = overlay->width;
1347   }
1348
1349   height =
1350       (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1351   if (height > overlay->height) {
1352     height = overlay->height;
1353   }
1354   if (overlay->use_vertical_render) {
1355     PangoRectangle rect;
1356     PangoContext *context;
1357     PangoMatrix matrix = PANGO_MATRIX_INIT;
1358     int tmp;
1359
1360     context = pango_layout_get_context (overlay->layout);
1361
1362     pango_matrix_rotate (&matrix, -90);
1363
1364     rect.x = rect.y = 0;
1365     rect.width = width;
1366     rect.height = height;
1367     pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1368     matrix.x0 = -rect.x;
1369     matrix.y0 = -rect.y;
1370
1371     pango_context_set_matrix (context, &matrix);
1372
1373     cairo_matrix.xx = matrix.xx;
1374     cairo_matrix.yx = matrix.yx;
1375     cairo_matrix.xy = matrix.xy;
1376     cairo_matrix.yy = matrix.yy;
1377     cairo_matrix.x0 = matrix.x0;
1378     cairo_matrix.y0 = matrix.y0;
1379     cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1380
1381     tmp = height;
1382     height = width;
1383     width = tmp;
1384   } else {
1385     cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1386   }
1387
1388   /* reallocate surface */
1389   overlay->text_image = g_realloc (overlay->text_image, 4 * width * height);
1390
1391   surface = cairo_image_surface_create_for_data (overlay->text_image,
1392       CAIRO_FORMAT_ARGB32, width, height, width * 4);
1393   cr = cairo_create (surface);
1394
1395   /* clear surface */
1396   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1397   cairo_paint (cr);
1398
1399   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1400
1401   if (overlay->want_shading)
1402     cairo_paint_with_alpha (cr, overlay->shading_value);
1403
1404   /* apply transformations */
1405   cairo_set_matrix (cr, &cairo_matrix);
1406
1407   /* FIXME: We use show_layout everywhere except for the surface
1408    * because it's really faster and internally does all kinds of
1409    * caching. Unfortunately we have to paint to a cairo path for
1410    * the outline and this is slow. Once Pango supports user fonts
1411    * we should use them, see
1412    * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1413    *
1414    * Idea would the be, to create a cairo user font that
1415    * does shadow, outline, text painting in the
1416    * render_glyph function.
1417    */
1418
1419   /* draw shadow text */
1420   cairo_save (cr);
1421   cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1422   cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1423   pango_cairo_show_layout (cr, overlay->layout);
1424   cairo_restore (cr);
1425
1426   a = (overlay->outline_color >> 24) & 0xff;
1427   r = (overlay->outline_color >> 16) & 0xff;
1428   g = (overlay->outline_color >> 8) & 0xff;
1429   b = (overlay->outline_color >> 0) & 0xff;
1430
1431   /* draw outline text */
1432   cairo_save (cr);
1433   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1434   cairo_set_line_width (cr, overlay->outline_offset);
1435   pango_cairo_layout_path (cr, overlay->layout);
1436   cairo_stroke (cr);
1437   cairo_restore (cr);
1438
1439   a = (overlay->color >> 24) & 0xff;
1440   r = (overlay->color >> 16) & 0xff;
1441   g = (overlay->color >> 8) & 0xff;
1442   b = (overlay->color >> 0) & 0xff;
1443
1444   /* draw text */
1445   cairo_save (cr);
1446   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1447   pango_cairo_show_layout (cr, overlay->layout);
1448   cairo_restore (cr);
1449
1450   cairo_destroy (cr);
1451   cairo_surface_destroy (surface);
1452   overlay->image_width = width;
1453   overlay->image_height = height;
1454   overlay->baseline_y = ink_rect.y;
1455   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1456 }
1457
1458 #define BOX_XPAD         6
1459 #define BOX_YPAD         6
1460
1461 static inline void
1462 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1463     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1464 {
1465   gint i, j, dest_stride;
1466   guint8 *dest_ptr;
1467
1468   dest_stride = dest->info.stride[0];
1469   dest_ptr = dest->data[0];
1470
1471   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1472   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1473
1474   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1475   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1476
1477   for (i = y0; i < y1; ++i) {
1478     for (j = x0; j < x1; ++j) {
1479       gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1480
1481       dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1482     }
1483   }
1484 }
1485
1486 static inline void
1487 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1488     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1489 {
1490   gint i, j;
1491   guint dest_stride, pixel_stride;
1492   guint8 *dest_ptr;
1493
1494   dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1495   dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1496   pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1497
1498   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1499   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1500
1501   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1502   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1503
1504   if (x0 != 0)
1505     x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1506   if (x1 != 0)
1507     x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1508
1509   if (y0 != 0)
1510     y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1511   if (y1 != 0)
1512     y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1513
1514   for (i = y0; i < y1; i++) {
1515     for (j = x0; j < x1; j++) {
1516       gint y;
1517       gint y_pos;
1518
1519       y_pos = (i * dest_stride) + j * pixel_stride;
1520       y = dest_ptr[y_pos] + overlay->shading_value;
1521
1522       dest_ptr[y_pos] = CLAMP (y, 0, 255);
1523     }
1524   }
1525 }
1526
1527 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1528 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1529 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1530 static inline void
1531 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1532     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1533 {
1534   gint i, j;
1535   guint8 *dest_ptr;
1536
1537   dest_ptr = dest->data[0];
1538
1539   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1540   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1541
1542   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1543   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1544
1545   for (i = y0; i < y1; i++) {
1546     for (j = x0; j < x1; j++) {
1547       gint y, y_pos, k;
1548
1549       y_pos = (i * 4 * overlay->width) + j * 4;
1550       for (k = 0; k < 4; k++) {
1551         y = dest_ptr[y_pos + k] + overlay->shading_value;
1552         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1553       }
1554     }
1555   }
1556 }
1557
1558 #define ARGB_SHADE_FUNCTION(name, OFFSET)       \
1559 static inline void \
1560 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1561 gint x0, gint x1, gint y0, gint y1) \
1562 { \
1563   gint i, j;\
1564   guint8 *dest_ptr;\
1565   \
1566   dest_ptr = dest->data[0];\
1567   \
1568   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1569   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1570   \
1571   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1572   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1573   \
1574   for (i = y0; i < y1; i++) {\
1575     for (j = x0; j < x1; j++) {\
1576       gint y, y_pos, k;\
1577       y_pos = (i * 4 * overlay->width) + j * 4;\
1578       for (k = OFFSET; k < 3+OFFSET; k++) {\
1579         y = dest_ptr[y_pos + k] + overlay->shading_value;\
1580         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1581       }\
1582     }\
1583   }\
1584 }
1585 ARGB_SHADE_FUNCTION (ARGB, 1);
1586 ARGB_SHADE_FUNCTION (ABGR, 1);
1587 ARGB_SHADE_FUNCTION (RGBA, 0);
1588 ARGB_SHADE_FUNCTION (BGRA, 0);
1589
1590
1591 /* FIXME:
1592  *  - use proper strides and offset for I420
1593  *  - don't draw over the edge of the picture (try a longer
1594  *    text with a huge font size)
1595  */
1596
1597 static inline void
1598 gst_base_text_overlay_blit_NV12_NV21 (GstBaseTextOverlay * overlay,
1599     GstVideoFrame * dest, gint xpos, gint ypos)
1600 {
1601   int y_stride, u_stride, v_stride;
1602   guint8 *y_pixels, *u_pixels, *v_pixels;
1603
1604   /* because U/V is 2x2 subsampled, we need to round, either up or down,
1605    * to a boundary of integer number of U/V pixels:
1606    */
1607   xpos = GST_ROUND_UP_2 (xpos);
1608   ypos = GST_ROUND_UP_2 (ypos);
1609
1610   y_pixels = dest->data[0];
1611   u_pixels = dest->data[1];
1612   v_pixels = dest->data[2];
1613   y_stride = dest->info.stride[0];
1614   u_stride = dest->info.stride[1];
1615   v_stride = dest->info.stride[2];
1616
1617   gst_base_text_overlay_blit_1 (overlay, y_pixels, xpos, ypos,
1618       overlay->text_image, y_stride);
1619   gst_base_text_overlay_blit_sub2x2cbcr (overlay, u_pixels,
1620       v_pixels, xpos, ypos, overlay->text_image, u_stride, v_stride, 2);
1621 }
1622
1623 static inline void
1624 gst_base_text_overlay_blit_I420 (GstBaseTextOverlay * overlay,
1625     GstVideoFrame * dest, gint xpos, gint ypos)
1626 {
1627   int y_stride, u_stride, v_stride;
1628   guint8 *y_pixels, *u_pixels, *v_pixels;
1629
1630   /* because U/V is 2x2 subsampled, we need to round, either up or down,
1631    * to a boundary of integer number of U/V pixels:
1632    */
1633   xpos = GST_ROUND_UP_2 (xpos);
1634   ypos = GST_ROUND_UP_2 (ypos);
1635
1636   y_pixels = dest->data[0];
1637   u_pixels = dest->data[1];
1638   v_pixels = dest->data[2];
1639   y_stride = dest->info.stride[0];
1640   u_stride = dest->info.stride[1];
1641   v_stride = dest->info.stride[2];
1642
1643   gst_base_text_overlay_blit_1 (overlay, y_pixels, xpos, ypos,
1644       overlay->text_image, y_stride);
1645   gst_base_text_overlay_blit_sub2x2cbcr (overlay, u_pixels,
1646       v_pixels, xpos, ypos, overlay->text_image, u_stride, v_stride, 1);
1647 }
1648
1649 static inline void
1650 gst_base_text_overlay_blit_UYVY (GstBaseTextOverlay * overlay,
1651     GstVideoFrame * dest, gint xpos, gint ypos)
1652 {
1653   int a0, r0, g0, b0;
1654   int a1, r1, g1, b1;
1655   int y0, y1, u, v;
1656   int i, j;
1657   int h, w;
1658   guchar *pimage, *dest_ptr;
1659   guint8 *yuv_pixels;
1660
1661   yuv_pixels = dest->data[0];
1662
1663   /* because U/V is 2x horizontally subsampled, we need to round to a
1664    * boundary of integer number of U/V pixels in x dimension:
1665    */
1666   xpos = GST_ROUND_UP_2 (xpos);
1667
1668   w = overlay->image_width - 2;
1669   h = overlay->image_height - 2;
1670
1671   if (xpos < 0) {
1672     xpos = 0;
1673   }
1674
1675   if (xpos + w > overlay->width) {
1676     w = overlay->width - xpos;
1677   }
1678
1679   if (ypos + h > overlay->height) {
1680     h = overlay->height - ypos;
1681   }
1682
1683   for (i = 0; i < h; i++) {
1684     pimage = overlay->text_image + i * overlay->image_width * 4;
1685     dest_ptr = yuv_pixels + (i + ypos) * overlay->width * 2 + xpos * 2;
1686     for (j = 0; j < w; j += 2) {
1687       b0 = pimage[CAIRO_ARGB_B];
1688       g0 = pimage[CAIRO_ARGB_G];
1689       r0 = pimage[CAIRO_ARGB_R];
1690       a0 = pimage[CAIRO_ARGB_A];
1691       CAIRO_UNPREMULTIPLY (a0, r0, g0, b0);
1692       pimage += 4;
1693
1694       b1 = pimage[CAIRO_ARGB_B];
1695       g1 = pimage[CAIRO_ARGB_G];
1696       r1 = pimage[CAIRO_ARGB_R];
1697       a1 = pimage[CAIRO_ARGB_A];
1698       CAIRO_UNPREMULTIPLY (a1, r1, g1, b1);
1699       pimage += 4;
1700
1701       a0 += a1 + 2;
1702       a0 /= 2;
1703       if (a0 == 0) {
1704         dest_ptr += 4;
1705         continue;
1706       }
1707
1708       COMP_Y (y0, r0, g0, b0);
1709       COMP_Y (y1, r1, g1, b1);
1710
1711       b0 += b1 + 2;
1712       g0 += g1 + 2;
1713       r0 += r1 + 2;
1714
1715       b0 /= 2;
1716       g0 /= 2;
1717       r0 /= 2;
1718
1719       COMP_U (u, r0, g0, b0);
1720       COMP_V (v, r0, g0, b0);
1721
1722       BLEND (*dest_ptr, a0, u, *dest_ptr);
1723       dest_ptr++;
1724       BLEND (*dest_ptr, a0, y0, *dest_ptr);
1725       dest_ptr++;
1726       BLEND (*dest_ptr, a0, v, *dest_ptr);
1727       dest_ptr++;
1728       BLEND (*dest_ptr, a0, y1, *dest_ptr);
1729       dest_ptr++;
1730     }
1731   }
1732 }
1733
1734 static inline void
1735 gst_base_text_overlay_blit_AYUV (GstBaseTextOverlay * overlay,
1736     GstVideoFrame * dest, gint xpos, gint ypos)
1737 {
1738   int a, r, g, b, a1;
1739   int y, u, v;
1740   int i, j;
1741   int h, w;
1742   guchar *pimage, *dest_ptr;
1743   guint8 *rgb_pixels;
1744
1745   rgb_pixels = dest->data[0];
1746
1747   w = overlay->image_width;
1748   h = overlay->image_height;
1749
1750   if (xpos < 0) {
1751     xpos = 0;
1752   }
1753
1754   if (xpos + w > overlay->width) {
1755     w = overlay->width - xpos;
1756   }
1757
1758   if (ypos + h > overlay->height) {
1759     h = overlay->height - ypos;
1760   }
1761
1762   for (i = 0; i < h; i++) {
1763     pimage = overlay->text_image + i * overlay->image_width * 4;
1764     dest_ptr = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4;
1765     for (j = 0; j < w; j++) {
1766       a = pimage[CAIRO_ARGB_A];
1767       b = pimage[CAIRO_ARGB_B];
1768       g = pimage[CAIRO_ARGB_G];
1769       r = pimage[CAIRO_ARGB_R];
1770
1771       CAIRO_UNPREMULTIPLY (a, r, g, b);
1772
1773       // convert background to yuv
1774       COMP_Y (y, r, g, b);
1775       COMP_U (u, r, g, b);
1776       COMP_V (v, r, g, b);
1777
1778       // preform text "OVER" background alpha compositing
1779       a1 = a + (dest_ptr[0] * (255 - a)) / 255 + 1;     // add 1 to prevent divide by 0
1780       OVER (dest_ptr[1], a, y, dest_ptr[0], dest_ptr[1], a1);
1781       OVER (dest_ptr[2], a, u, dest_ptr[0], dest_ptr[2], a1);
1782       OVER (dest_ptr[3], a, v, dest_ptr[0], dest_ptr[3], a1);
1783       dest_ptr[0] = a1 - 1;     // remove the temporary 1 we added
1784
1785       pimage += 4;
1786       dest_ptr += 4;
1787     }
1788   }
1789 }
1790
1791 #define xRGB_BLIT_FUNCTION(name, R, G, B) \
1792 static inline void \
1793 gst_base_text_overlay_blit_##name (GstBaseTextOverlay * overlay, \
1794     GstVideoFrame * dest, gint xpos, gint ypos) \
1795 { \
1796   int a, r, g, b; \
1797   int i, j; \
1798   int h, w; \
1799   guchar *pimage, *dest_ptr; \
1800   guint8 *rgb_pixels;\
1801   \
1802   rgb_pixels = dest->data[0];\
1803   \
1804   w = overlay->image_width; \
1805   h = overlay->image_height; \
1806   \
1807   if (xpos < 0) { \
1808     xpos = 0; \
1809   } \
1810   \
1811   if (xpos + w > overlay->width) { \
1812     w = overlay->width - xpos; \
1813   } \
1814   \
1815   if (ypos + h > overlay->height) { \
1816     h = overlay->height - ypos; \
1817   } \
1818   \
1819   for (i = 0; i < h; i++) { \
1820     pimage = overlay->text_image + i * overlay->image_width * 4; \
1821     dest_ptr = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; \
1822     for (j = 0; j < w; j++) { \
1823       a = pimage[CAIRO_ARGB_A]; \
1824       b = pimage[CAIRO_ARGB_B]; \
1825       g = pimage[CAIRO_ARGB_G]; \
1826       r = pimage[CAIRO_ARGB_R]; \
1827       CAIRO_UNPREMULTIPLY (a, r, g, b); \
1828       b = (b*a + dest_ptr[B] * (255-a)) / 255; \
1829       g = (g*a + dest_ptr[G] * (255-a)) / 255; \
1830       r = (r*a + dest_ptr[R] * (255-a)) / 255; \
1831       \
1832       dest_ptr[B] = b; \
1833       dest_ptr[G] = g; \
1834       dest_ptr[R] = r; \
1835       pimage += 4; \
1836       dest_ptr += 4; \
1837     } \
1838   } \
1839 }
1840 xRGB_BLIT_FUNCTION (xRGB, 1, 2, 3);
1841 xRGB_BLIT_FUNCTION (BGRx, 2, 1, 0);
1842 xRGB_BLIT_FUNCTION (xBGR, 3, 2, 1);
1843 xRGB_BLIT_FUNCTION (RGBx, 0, 1, 2);
1844
1845 #define ARGB_BLIT_FUNCTION(name, A, R, G, B)    \
1846 static inline void \
1847 gst_base_text_overlay_blit_##name (GstBaseTextOverlay * overlay, \
1848     GstVideoFrame * dest, gint xpos, gint ypos) \
1849 { \
1850   int a, r, g, b, a1;                           \
1851   int i, j; \
1852   int h, w; \
1853   guchar *pimage, *dest_ptr; \
1854   guint8 *rgb_pixels;\
1855   \
1856   rgb_pixels = dest->data[0];\
1857   \
1858   w = overlay->image_width; \
1859   h = overlay->image_height; \
1860   \
1861   if (xpos < 0) { \
1862     xpos = 0; \
1863   } \
1864   \
1865   if (xpos + w > overlay->width) { \
1866     w = overlay->width - xpos; \
1867   } \
1868   \
1869   if (ypos + h > overlay->height) { \
1870     h = overlay->height - ypos; \
1871   } \
1872   \
1873   for (i = 0; i < h; i++) { \
1874     pimage = overlay->text_image + i * overlay->image_width * 4; \
1875     dest_ptr = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; \
1876     for (j = 0; j < w; j++) { \
1877       a = pimage[CAIRO_ARGB_A]; \
1878       b = pimage[CAIRO_ARGB_B]; \
1879       g = pimage[CAIRO_ARGB_G]; \
1880       r = pimage[CAIRO_ARGB_R]; \
1881       CAIRO_UNPREMULTIPLY (a, r, g, b); \
1882       a1 = a + (dest_ptr[A] * (255 - a)) / 255 + 1; \
1883       OVER (dest_ptr[R], a, r, dest_ptr[0], dest_ptr[R], a1); \
1884       OVER (dest_ptr[G], a, g, dest_ptr[0], dest_ptr[G], a1); \
1885       OVER (dest_ptr[B], a, b, dest_ptr[0], dest_ptr[B], a1); \
1886       dest_ptr[A] = a1 - 1; \
1887       pimage += 4; \
1888       dest_ptr += 4; \
1889     } \
1890   } \
1891 }
1892 ARGB_BLIT_FUNCTION (RGBA, 3, 0, 1, 2);
1893 ARGB_BLIT_FUNCTION (BGRA, 3, 2, 1, 0);
1894 ARGB_BLIT_FUNCTION (ARGB, 0, 1, 2, 3);
1895 ARGB_BLIT_FUNCTION (ABGR, 0, 3, 2, 1);
1896
1897 static void
1898 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1899     const gchar * text, gint textlen)
1900 {
1901   gchar *string;
1902
1903   if (!overlay->need_render) {
1904     GST_DEBUG ("Using previously rendered text.");
1905     return;
1906   }
1907
1908   /* -1 is the whole string */
1909   if (text != NULL && textlen < 0) {
1910     textlen = strlen (text);
1911   }
1912
1913   if (text != NULL) {
1914     string = g_strndup (text, textlen);
1915   } else {                      /* empty string */
1916     string = g_strdup (" ");
1917   }
1918   g_strdelimit (string, "\r\t", ' ');
1919   textlen = strlen (string);
1920
1921   /* FIXME: should we check for UTF-8 here? */
1922
1923   GST_DEBUG ("Rendering '%s'", string);
1924   gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1925
1926   g_free (string);
1927
1928   overlay->need_render = FALSE;
1929 }
1930
1931 static GstFlowReturn
1932 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1933     GstBuffer * video_frame)
1934 {
1935   gint xpos, ypos;
1936   gint width, height;
1937   GstBaseTextOverlayVAlign valign;
1938   GstBaseTextOverlayHAlign halign;
1939   GstVideoFrame frame;
1940
1941   width = overlay->image_width;
1942   height = overlay->image_height;
1943
1944   video_frame = gst_buffer_make_writable (video_frame);
1945
1946   if (!gst_video_frame_map (&frame, &overlay->info, video_frame, GST_MAP_WRITE))
1947     goto invalid_frame;
1948
1949   if (overlay->use_vertical_render)
1950     halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1951   else
1952     halign = overlay->halign;
1953
1954   switch (halign) {
1955     case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1956       xpos = overlay->xpad;
1957       break;
1958     case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1959       xpos = (overlay->width - width) / 2;
1960       break;
1961     case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1962       xpos = overlay->width - width - overlay->xpad;
1963       break;
1964     case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1965       xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1966       xpos = CLAMP (xpos, 0, overlay->width - width);
1967       if (xpos < 0)
1968         xpos = 0;
1969       break;
1970     default:
1971       xpos = 0;
1972   }
1973   xpos += overlay->deltax;
1974
1975   if (overlay->use_vertical_render)
1976     valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1977   else
1978     valign = overlay->valign;
1979
1980   switch (valign) {
1981     case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1982       ypos = overlay->height - height - overlay->ypad;
1983       break;
1984     case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1985       ypos = overlay->height - (height + overlay->ypad);
1986       break;
1987     case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1988       ypos = overlay->ypad;
1989       break;
1990     case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1991       ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1992       ypos = CLAMP (ypos, 0, overlay->height - height);
1993       break;
1994     case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1995       ypos = (overlay->height - height) / 2;
1996       break;
1997     default:
1998       ypos = overlay->ypad;
1999       break;
2000   }
2001   ypos += overlay->deltay;
2002
2003   /* shaded background box */
2004   if (overlay->want_shading) {
2005     switch (overlay->format) {
2006       case GST_VIDEO_FORMAT_I420:
2007       case GST_VIDEO_FORMAT_NV12:
2008       case GST_VIDEO_FORMAT_NV21:
2009         gst_base_text_overlay_shade_planar_Y (overlay, &frame,
2010             xpos, xpos + overlay->image_width,
2011             ypos, ypos + overlay->image_height);
2012         break;
2013       case GST_VIDEO_FORMAT_AYUV:
2014       case GST_VIDEO_FORMAT_UYVY:
2015         gst_base_text_overlay_shade_packed_Y (overlay, &frame,
2016             xpos, xpos + overlay->image_width,
2017             ypos, ypos + overlay->image_height);
2018         break;
2019       case GST_VIDEO_FORMAT_xRGB:
2020         gst_base_text_overlay_shade_xRGB (overlay, &frame,
2021             xpos, xpos + overlay->image_width,
2022             ypos, ypos + overlay->image_height);
2023         break;
2024       case GST_VIDEO_FORMAT_xBGR:
2025         gst_base_text_overlay_shade_xBGR (overlay, &frame,
2026             xpos, xpos + overlay->image_width,
2027             ypos, ypos + overlay->image_height);
2028         break;
2029       case GST_VIDEO_FORMAT_BGRx:
2030         gst_base_text_overlay_shade_BGRx (overlay, &frame,
2031             xpos, xpos + overlay->image_width,
2032             ypos, ypos + overlay->image_height);
2033         break;
2034       case GST_VIDEO_FORMAT_RGBx:
2035         gst_base_text_overlay_shade_RGBx (overlay, &frame,
2036             xpos, xpos + overlay->image_width,
2037             ypos, ypos + overlay->image_height);
2038         break;
2039       case GST_VIDEO_FORMAT_ARGB:
2040         gst_base_text_overlay_shade_ARGB (overlay, &frame,
2041             xpos, xpos + overlay->image_width,
2042             ypos, ypos + overlay->image_height);
2043         break;
2044       case GST_VIDEO_FORMAT_ABGR:
2045         gst_base_text_overlay_shade_ABGR (overlay, &frame,
2046             xpos, xpos + overlay->image_width,
2047             ypos, ypos + overlay->image_height);
2048         break;
2049       case GST_VIDEO_FORMAT_RGBA:
2050         gst_base_text_overlay_shade_RGBA (overlay, &frame,
2051             xpos, xpos + overlay->image_width,
2052             ypos, ypos + overlay->image_height);
2053         break;
2054       case GST_VIDEO_FORMAT_BGRA:
2055         gst_base_text_overlay_shade_BGRA (overlay, &frame,
2056             xpos, xpos + overlay->image_width,
2057             ypos, ypos + overlay->image_height);
2058         break;
2059       default:
2060         g_assert_not_reached ();
2061     }
2062   }
2063
2064   if (ypos < 0)
2065     ypos = 0;
2066
2067   if (overlay->text_image) {
2068     switch (overlay->format) {
2069       case GST_VIDEO_FORMAT_I420:
2070         gst_base_text_overlay_blit_I420 (overlay, &frame, xpos, ypos);
2071         break;
2072       case GST_VIDEO_FORMAT_NV12:
2073       case GST_VIDEO_FORMAT_NV21:
2074         gst_base_text_overlay_blit_NV12_NV21 (overlay, &frame, xpos, ypos);
2075         break;
2076       case GST_VIDEO_FORMAT_UYVY:
2077         gst_base_text_overlay_blit_UYVY (overlay, &frame, xpos, ypos);
2078         break;
2079       case GST_VIDEO_FORMAT_AYUV:
2080         gst_base_text_overlay_blit_AYUV (overlay, &frame, xpos, ypos);
2081         break;
2082       case GST_VIDEO_FORMAT_BGRx:
2083         gst_base_text_overlay_blit_BGRx (overlay, &frame, xpos, ypos);
2084         break;
2085       case GST_VIDEO_FORMAT_xRGB:
2086         gst_base_text_overlay_blit_xRGB (overlay, &frame, xpos, ypos);
2087         break;
2088       case GST_VIDEO_FORMAT_RGBx:
2089         gst_base_text_overlay_blit_RGBx (overlay, &frame, xpos, ypos);
2090         break;
2091       case GST_VIDEO_FORMAT_xBGR:
2092         gst_base_text_overlay_blit_xBGR (overlay, &frame, xpos, ypos);
2093         break;
2094       case GST_VIDEO_FORMAT_ARGB:
2095         gst_base_text_overlay_blit_ARGB (overlay, &frame, xpos, ypos);
2096         break;
2097       case GST_VIDEO_FORMAT_ABGR:
2098         gst_base_text_overlay_blit_ABGR (overlay, &frame, xpos, ypos);
2099         break;
2100       case GST_VIDEO_FORMAT_RGBA:
2101         gst_base_text_overlay_blit_RGBA (overlay, &frame, xpos, ypos);
2102         break;
2103       case GST_VIDEO_FORMAT_BGRA:
2104         gst_base_text_overlay_blit_BGRA (overlay, &frame, xpos, ypos);
2105         break;
2106       default:
2107         g_assert_not_reached ();
2108     }
2109   }
2110   gst_video_frame_unmap (&frame);
2111
2112   return gst_pad_push (overlay->srcpad, video_frame);
2113
2114   /* ERRORS */
2115 invalid_frame:
2116   {
2117     GST_DEBUG_OBJECT (overlay, "received invalid buffer");
2118     return GST_FLOW_OK;
2119   }
2120 }
2121
2122 static GstPadLinkReturn
2123 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
2124 {
2125   GstBaseTextOverlay *overlay;
2126
2127   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
2128   if (G_UNLIKELY (!overlay))
2129     return GST_PAD_LINK_REFUSED;
2130
2131   GST_DEBUG_OBJECT (overlay, "Text pad linked");
2132
2133   overlay->text_linked = TRUE;
2134
2135   gst_object_unref (overlay);
2136
2137   return GST_PAD_LINK_OK;
2138 }
2139
2140 static void
2141 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
2142 {
2143   GstBaseTextOverlay *overlay;
2144
2145   /* don't use gst_pad_get_parent() here, will deadlock */
2146   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
2147
2148   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
2149
2150   overlay->text_linked = FALSE;
2151
2152   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
2153 }
2154
2155 static gboolean
2156 gst_base_text_overlay_text_event (GstPad * pad, GstEvent * event)
2157 {
2158   gboolean ret = FALSE;
2159   GstBaseTextOverlay *overlay = NULL;
2160
2161   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
2162   if (G_UNLIKELY (!overlay)) {
2163     gst_event_unref (event);
2164     return FALSE;
2165   }
2166
2167   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2168
2169   switch (GST_EVENT_TYPE (event)) {
2170     case GST_EVENT_CAPS:
2171     {
2172       GstCaps *caps;
2173
2174       gst_event_parse_caps (event, &caps);
2175       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
2176       gst_event_unref (event);
2177       break;
2178     }
2179     case GST_EVENT_SEGMENT:
2180     {
2181       const GstSegment *segment;
2182
2183       overlay->text_eos = FALSE;
2184
2185       gst_event_parse_segment (event, &segment);
2186
2187       if (segment->format == GST_FORMAT_TIME) {
2188         GST_OBJECT_LOCK (overlay);
2189         gst_segment_copy_into (segment, &overlay->text_segment);
2190         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
2191             &overlay->text_segment);
2192         GST_OBJECT_UNLOCK (overlay);
2193       } else {
2194         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2195             ("received non-TIME newsegment event on text input"));
2196       }
2197
2198       gst_event_unref (event);
2199       ret = TRUE;
2200
2201       /* wake up the video chain, it might be waiting for a text buffer or
2202        * a text segment update */
2203       GST_OBJECT_LOCK (overlay);
2204       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2205       GST_OBJECT_UNLOCK (overlay);
2206       break;
2207     }
2208     case GST_EVENT_FLUSH_STOP:
2209       GST_OBJECT_LOCK (overlay);
2210       GST_INFO_OBJECT (overlay, "text flush stop");
2211       overlay->text_flushing = FALSE;
2212       overlay->text_eos = FALSE;
2213       gst_base_text_overlay_pop_text (overlay);
2214       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2215       GST_OBJECT_UNLOCK (overlay);
2216       gst_event_unref (event);
2217       ret = TRUE;
2218       break;
2219     case GST_EVENT_FLUSH_START:
2220       GST_OBJECT_LOCK (overlay);
2221       GST_INFO_OBJECT (overlay, "text flush start");
2222       overlay->text_flushing = TRUE;
2223       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2224       GST_OBJECT_UNLOCK (overlay);
2225       gst_event_unref (event);
2226       ret = TRUE;
2227       break;
2228     case GST_EVENT_EOS:
2229       GST_OBJECT_LOCK (overlay);
2230       overlay->text_eos = TRUE;
2231       GST_INFO_OBJECT (overlay, "text EOS");
2232       /* wake up the video chain, it might be waiting for a text buffer or
2233        * a text segment update */
2234       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2235       GST_OBJECT_UNLOCK (overlay);
2236       gst_event_unref (event);
2237       ret = TRUE;
2238       break;
2239     default:
2240       ret = gst_pad_event_default (pad, event);
2241       break;
2242   }
2243
2244   gst_object_unref (overlay);
2245
2246   return ret;
2247 }
2248
2249 static gboolean
2250 gst_base_text_overlay_video_event (GstPad * pad, GstEvent * event)
2251 {
2252   gboolean ret = FALSE;
2253   GstBaseTextOverlay *overlay = NULL;
2254
2255   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
2256   if (G_UNLIKELY (!overlay)) {
2257     gst_event_unref (event);
2258     return FALSE;
2259   }
2260
2261   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2262
2263   switch (GST_EVENT_TYPE (event)) {
2264     case GST_EVENT_CAPS:
2265     {
2266       GstCaps *caps;
2267
2268       gst_event_parse_caps (event, &caps);
2269       ret = gst_base_text_overlay_setcaps (overlay, caps);
2270       gst_event_unref (event);
2271       break;
2272     }
2273     case GST_EVENT_SEGMENT:
2274     {
2275       const GstSegment *segment;
2276
2277       GST_DEBUG_OBJECT (overlay, "received new segment");
2278
2279       gst_event_parse_segment (event, &segment);
2280
2281       if (segment->format == GST_FORMAT_TIME) {
2282         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
2283             &overlay->segment);
2284
2285         gst_segment_copy_into (segment, &overlay->segment);
2286       } else {
2287         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2288             ("received non-TIME newsegment event on video input"));
2289       }
2290
2291       ret = gst_pad_event_default (pad, event);
2292       break;
2293     }
2294     case GST_EVENT_EOS:
2295       GST_OBJECT_LOCK (overlay);
2296       GST_INFO_OBJECT (overlay, "video EOS");
2297       overlay->video_eos = TRUE;
2298       GST_OBJECT_UNLOCK (overlay);
2299       ret = gst_pad_event_default (pad, event);
2300       break;
2301     case GST_EVENT_FLUSH_START:
2302       GST_OBJECT_LOCK (overlay);
2303       GST_INFO_OBJECT (overlay, "video flush start");
2304       overlay->video_flushing = TRUE;
2305       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2306       GST_OBJECT_UNLOCK (overlay);
2307       ret = gst_pad_event_default (pad, event);
2308       break;
2309     case GST_EVENT_FLUSH_STOP:
2310       GST_OBJECT_LOCK (overlay);
2311       GST_INFO_OBJECT (overlay, "video flush stop");
2312       overlay->video_flushing = FALSE;
2313       overlay->video_eos = FALSE;
2314       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2315       GST_OBJECT_UNLOCK (overlay);
2316       ret = gst_pad_event_default (pad, event);
2317       break;
2318     default:
2319       ret = gst_pad_event_default (pad, event);
2320       break;
2321   }
2322
2323   gst_object_unref (overlay);
2324
2325   return ret;
2326 }
2327
2328 static gboolean
2329 gst_base_text_overlay_video_query (GstPad * pad, GstQuery * query)
2330 {
2331   gboolean ret = FALSE;
2332   GstBaseTextOverlay *overlay = NULL;
2333
2334   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
2335   if (G_UNLIKELY (!overlay)) {
2336     return FALSE;
2337   }
2338
2339   switch (GST_QUERY_TYPE (query)) {
2340     case GST_QUERY_CAPS:
2341     {
2342       GstCaps *filter, *caps;
2343
2344       gst_query_parse_caps (query, &filter);
2345       caps = gst_base_text_overlay_getcaps (pad, filter);
2346       gst_query_set_caps_result (query, caps);
2347       gst_caps_unref (caps);
2348       ret = TRUE;
2349       break;
2350     }
2351     default:
2352       ret = gst_pad_query_default (pad, query);
2353       break;
2354   }
2355
2356   gst_object_unref (overlay);
2357
2358   return ret;
2359 }
2360
2361 /* Called with lock held */
2362 static void
2363 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2364 {
2365   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2366
2367   if (overlay->text_buffer) {
2368     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2369         overlay->text_buffer);
2370     gst_buffer_unref (overlay->text_buffer);
2371     overlay->text_buffer = NULL;
2372   }
2373
2374   /* Let the text task know we used that buffer */
2375   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2376 }
2377
2378 /* We receive text buffers here. If they are out of segment we just ignore them.
2379    If the buffer is in our segment we keep it internally except if another one
2380    is already waiting here, in that case we wait that it gets kicked out */
2381 static GstFlowReturn
2382 gst_base_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer)
2383 {
2384   GstFlowReturn ret = GST_FLOW_OK;
2385   GstBaseTextOverlay *overlay = NULL;
2386   gboolean in_seg = FALSE;
2387   guint64 clip_start = 0, clip_stop = 0;
2388
2389   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
2390
2391   GST_OBJECT_LOCK (overlay);
2392
2393   if (overlay->text_flushing) {
2394     GST_OBJECT_UNLOCK (overlay);
2395     ret = GST_FLOW_WRONG_STATE;
2396     GST_LOG_OBJECT (overlay, "text flushing");
2397     goto beach;
2398   }
2399
2400   if (overlay->text_eos) {
2401     GST_OBJECT_UNLOCK (overlay);
2402     ret = GST_FLOW_EOS;
2403     GST_LOG_OBJECT (overlay, "text EOS");
2404     goto beach;
2405   }
2406
2407   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2408       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2409       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2410       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2411           GST_BUFFER_DURATION (buffer)));
2412
2413   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2414     GstClockTime stop;
2415
2416     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2417       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2418     else
2419       stop = GST_CLOCK_TIME_NONE;
2420
2421     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2422         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2423   } else {
2424     in_seg = TRUE;
2425   }
2426
2427   if (in_seg) {
2428     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2429       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2430     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2431       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2432
2433     /* Wait for the previous buffer to go away */
2434     while (overlay->text_buffer != NULL) {
2435       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2436           GST_DEBUG_PAD_NAME (pad));
2437       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2438       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2439       if (overlay->text_flushing) {
2440         GST_OBJECT_UNLOCK (overlay);
2441         ret = GST_FLOW_WRONG_STATE;
2442         goto beach;
2443       }
2444     }
2445
2446     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2447       overlay->text_segment.position = clip_start;
2448
2449     overlay->text_buffer = buffer;
2450     /* That's a new text buffer we need to render */
2451     overlay->need_render = TRUE;
2452
2453     /* in case the video chain is waiting for a text buffer, wake it up */
2454     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2455   }
2456
2457   GST_OBJECT_UNLOCK (overlay);
2458
2459 beach:
2460
2461   return ret;
2462 }
2463
2464 static GstFlowReturn
2465 gst_base_text_overlay_video_chain (GstPad * pad, GstBuffer * buffer)
2466 {
2467   GstBaseTextOverlayClass *klass;
2468   GstBaseTextOverlay *overlay;
2469   GstFlowReturn ret = GST_FLOW_OK;
2470   gboolean in_seg = FALSE;
2471   guint64 start, stop, clip_start = 0, clip_stop = 0;
2472   gchar *text = NULL;
2473
2474   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
2475   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2476
2477   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2478     goto missing_timestamp;
2479
2480   /* ignore buffers that are outside of the current segment */
2481   start = GST_BUFFER_TIMESTAMP (buffer);
2482
2483   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2484     stop = GST_CLOCK_TIME_NONE;
2485   } else {
2486     stop = start + GST_BUFFER_DURATION (buffer);
2487   }
2488
2489   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2490       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2491       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2492
2493   /* segment_clip() will adjust start unconditionally to segment_start if
2494    * no stop time is provided, so handle this ourselves */
2495   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2496     goto out_of_segment;
2497
2498   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2499       &clip_start, &clip_stop);
2500
2501   if (!in_seg)
2502     goto out_of_segment;
2503
2504   /* if the buffer is only partially in the segment, fix up stamps */
2505   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2506     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2507     buffer = gst_buffer_make_writable (buffer);
2508     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2509     if (stop != -1)
2510       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2511   }
2512
2513   /* now, after we've done the clipping, fix up end time if there's no
2514    * duration (we only use those estimated values internally though, we
2515    * don't want to set bogus values on the buffer itself) */
2516   if (stop == -1) {
2517     GstCaps *caps;
2518     GstStructure *s;
2519     gint fps_num, fps_denom;
2520
2521     /* FIXME, store this in setcaps */
2522     caps = gst_pad_get_current_caps (pad);
2523     s = gst_caps_get_structure (caps, 0);
2524     if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2525         fps_num && fps_denom) {
2526       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2527       stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2528     } else {
2529       GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2530       stop = start + 1;         /* we need to assume some interval */
2531     }
2532     gst_caps_unref (caps);
2533   }
2534
2535   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2536
2537 wait_for_text_buf:
2538
2539   GST_OBJECT_LOCK (overlay);
2540
2541   if (overlay->video_flushing)
2542     goto flushing;
2543
2544   if (overlay->video_eos)
2545     goto have_eos;
2546
2547   if (overlay->silent) {
2548     GST_OBJECT_UNLOCK (overlay);
2549     ret = gst_pad_push (overlay->srcpad, buffer);
2550
2551     /* Update position */
2552     overlay->segment.position = clip_start;
2553
2554     return ret;
2555   }
2556
2557   /* Text pad not linked, rendering internal text */
2558   if (!overlay->text_linked) {
2559     if (klass->get_text) {
2560       text = klass->get_text (overlay, buffer);
2561     } else {
2562       text = g_strdup (overlay->default_text);
2563     }
2564
2565     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2566         "text: '%s'", GST_STR_NULL (text));
2567
2568     GST_OBJECT_UNLOCK (overlay);
2569
2570     if (text != NULL && *text != '\0') {
2571       /* Render and push */
2572       gst_base_text_overlay_render_text (overlay, text, -1);
2573       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2574     } else {
2575       /* Invalid or empty string */
2576       ret = gst_pad_push (overlay->srcpad, buffer);
2577     }
2578   } else {
2579     /* Text pad linked, check if we have a text buffer queued */
2580     if (overlay->text_buffer) {
2581       gboolean pop_text = FALSE, valid_text_time = TRUE;
2582       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2583       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2584       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2585       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2586       GstClockTime vid_running_time, vid_running_time_end;
2587
2588       /* if the text buffer isn't stamped right, pop it off the
2589        * queue and display it for the current video frame only */
2590       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2591           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2592         GST_WARNING_OBJECT (overlay,
2593             "Got text buffer with invalid timestamp or duration");
2594         pop_text = TRUE;
2595         valid_text_time = FALSE;
2596       } else {
2597         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2598         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2599       }
2600
2601       vid_running_time =
2602           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2603           start);
2604       vid_running_time_end =
2605           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2606           stop);
2607
2608       /* If timestamp and duration are valid */
2609       if (valid_text_time) {
2610         text_running_time =
2611             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2612             text_start);
2613         text_running_time_end =
2614             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2615             text_end);
2616       }
2617
2618       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2619           GST_TIME_ARGS (text_running_time),
2620           GST_TIME_ARGS (text_running_time_end));
2621       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2622           GST_TIME_ARGS (vid_running_time),
2623           GST_TIME_ARGS (vid_running_time_end));
2624
2625       /* Text too old or in the future */
2626       if (valid_text_time && text_running_time_end <= vid_running_time) {
2627         /* text buffer too old, get rid of it and do nothing  */
2628         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2629         pop_text = FALSE;
2630         gst_base_text_overlay_pop_text (overlay);
2631         GST_OBJECT_UNLOCK (overlay);
2632         goto wait_for_text_buf;
2633       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2634         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2635         GST_OBJECT_UNLOCK (overlay);
2636         /* Push the video frame */
2637         ret = gst_pad_push (overlay->srcpad, buffer);
2638       } else {
2639         gchar *in_text, *otext;
2640         gsize in_size, osize;
2641
2642         otext =
2643             gst_buffer_map (overlay->text_buffer, &osize, NULL, GST_MAP_READ);
2644         in_text = otext;
2645         in_size = osize;
2646
2647         /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2648          * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2649          * here on purpose, this is something that needs fixing upstream */
2650         if (!g_utf8_validate (in_text, in_size, NULL)) {
2651           const gchar *end = NULL;
2652
2653           GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2654           in_text = g_strndup (in_text, in_size);
2655           while (!g_utf8_validate (in_text, in_size, &end) && end)
2656             *((gchar *) end) = '*';
2657         }
2658
2659         /* Get the string */
2660         if (overlay->have_pango_markup) {
2661           text = g_strndup (in_text, in_size);
2662         } else {
2663           text = g_markup_escape_text (in_text, in_size);
2664         }
2665
2666         if (text != NULL && *text != '\0') {
2667           gint text_len = strlen (text);
2668
2669           while (text_len > 0 && (text[text_len - 1] == '\n' ||
2670                   text[text_len - 1] == '\r')) {
2671             --text_len;
2672           }
2673           GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2674           gst_base_text_overlay_render_text (overlay, text, text_len);
2675         } else {
2676           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2677           gst_base_text_overlay_render_text (overlay, " ", 1);
2678         }
2679         gst_buffer_unmap (overlay->text_buffer, otext, osize);
2680
2681         if (in_text != otext)
2682           g_free (in_text);
2683
2684         GST_OBJECT_UNLOCK (overlay);
2685         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2686
2687         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2688           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2689           pop_text = TRUE;
2690         }
2691       }
2692       if (pop_text) {
2693         GST_OBJECT_LOCK (overlay);
2694         gst_base_text_overlay_pop_text (overlay);
2695         GST_OBJECT_UNLOCK (overlay);
2696       }
2697     } else {
2698       gboolean wait_for_text_buf = TRUE;
2699
2700       if (overlay->text_eos)
2701         wait_for_text_buf = FALSE;
2702
2703       if (!overlay->wait_text)
2704         wait_for_text_buf = FALSE;
2705
2706       /* Text pad linked, but no text buffer available - what now? */
2707       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2708         GstClockTime text_start_running_time, text_position_running_time;
2709         GstClockTime vid_running_time;
2710
2711         vid_running_time =
2712             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2713             GST_BUFFER_TIMESTAMP (buffer));
2714         text_start_running_time =
2715             gst_segment_to_running_time (&overlay->text_segment,
2716             GST_FORMAT_TIME, overlay->text_segment.start);
2717         text_position_running_time =
2718             gst_segment_to_running_time (&overlay->text_segment,
2719             GST_FORMAT_TIME, overlay->text_segment.position);
2720
2721         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2722                 vid_running_time < text_start_running_time) ||
2723             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2724                 vid_running_time < text_position_running_time)) {
2725           wait_for_text_buf = FALSE;
2726         }
2727       }
2728
2729       if (wait_for_text_buf) {
2730         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2731         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2732         GST_DEBUG_OBJECT (overlay, "resuming");
2733         GST_OBJECT_UNLOCK (overlay);
2734         goto wait_for_text_buf;
2735       } else {
2736         GST_OBJECT_UNLOCK (overlay);
2737         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2738         ret = gst_pad_push (overlay->srcpad, buffer);
2739       }
2740     }
2741   }
2742
2743   g_free (text);
2744
2745   /* Update position */
2746   overlay->segment.position = clip_start;
2747
2748   return ret;
2749
2750 missing_timestamp:
2751   {
2752     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2753     gst_buffer_unref (buffer);
2754     return GST_FLOW_OK;
2755   }
2756
2757 flushing:
2758   {
2759     GST_OBJECT_UNLOCK (overlay);
2760     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2761     gst_buffer_unref (buffer);
2762     return GST_FLOW_WRONG_STATE;
2763   }
2764 have_eos:
2765   {
2766     GST_OBJECT_UNLOCK (overlay);
2767     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2768     gst_buffer_unref (buffer);
2769     return GST_FLOW_EOS;
2770   }
2771 out_of_segment:
2772   {
2773     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2774     gst_buffer_unref (buffer);
2775     return GST_FLOW_OK;
2776   }
2777 }
2778
2779 static GstStateChangeReturn
2780 gst_base_text_overlay_change_state (GstElement * element,
2781     GstStateChange transition)
2782 {
2783   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2784   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2785
2786   switch (transition) {
2787     case GST_STATE_CHANGE_PAUSED_TO_READY:
2788       GST_OBJECT_LOCK (overlay);
2789       overlay->text_flushing = TRUE;
2790       overlay->video_flushing = TRUE;
2791       /* pop_text will broadcast on the GCond and thus also make the video
2792        * chain exit if it's waiting for a text buffer */
2793       gst_base_text_overlay_pop_text (overlay);
2794       GST_OBJECT_UNLOCK (overlay);
2795       break;
2796     default:
2797       break;
2798   }
2799
2800   ret = parent_class->change_state (element, transition);
2801   if (ret == GST_STATE_CHANGE_FAILURE)
2802     return ret;
2803
2804   switch (transition) {
2805     case GST_STATE_CHANGE_READY_TO_PAUSED:
2806       GST_OBJECT_LOCK (overlay);
2807       overlay->text_flushing = FALSE;
2808       overlay->video_flushing = FALSE;
2809       overlay->video_eos = FALSE;
2810       overlay->text_eos = FALSE;
2811       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2812       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2813       GST_OBJECT_UNLOCK (overlay);
2814       break;
2815     default:
2816       break;
2817   }
2818
2819   return ret;
2820 }
2821
2822 static gboolean
2823 plugin_init (GstPlugin * plugin)
2824 {
2825   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2826           GST_TYPE_TEXT_OVERLAY) ||
2827       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2828           GST_TYPE_TIME_OVERLAY) ||
2829       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2830           GST_TYPE_CLOCK_OVERLAY) ||
2831       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2832           GST_TYPE_TEXT_RENDER)) {
2833     return FALSE;
2834   }
2835
2836   /*texttestsrc_plugin_init(module, plugin); */
2837
2838   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2839
2840   return TRUE;
2841 }
2842
2843 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2844     "pango", "Pango-based text rendering and overlay", plugin_init,
2845     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)