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>
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.
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.
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.
26 * SECTION:element-textoverlay
27 * @see_also: #GstTextRender, #GstClockOverlay, #GstTimeOverlay, #GstSubParse
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.
36 * The text can contain newline characters and text wrapping is enabled by
40 * <title>Example launch lines</title>
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
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:
51 * If you do not have such a subtitle file, create one looking like this
55 * 00:00:03,000 --> 00:00:05,000
59 * 00:00:08,000 --> 00:00:13,000
60 * Yes, this is a subtitle. Don't
61 * you like it? (8-13s)
64 * 00:00:18,826 --> 00:01:02,886
65 * Uh? What are you talking about?
66 * I don't understand (18-62s)
72 /* FIXME: alloc segment as part of instance struct */
78 #include <gst/video/video.h>
80 #include "gstbasetextoverlay.h"
81 #include "gsttextoverlay.h"
82 #include "gsttimeoverlay.h"
83 #include "gstclockoverlay.h"
84 #include "gsttextrender.h"
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)?
94 GST_DEBUG_CATEGORY (pango_debug);
95 #define GST_CAT_DEFAULT pango_debug
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
119 /* make a property of me */
120 #define DEFAULT_SHADING_VALUE -80
122 #define MINIMUM_OUTLINE_OFFSET 1.0
123 #define DEFAULT_SCALE_BASIS 640
125 #define COMP_Y(ret, r, g, b) \
127 ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
128 ret = CLAMP (ret, 0, 255); \
131 #define COMP_U(ret, r, g, b) \
133 ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
134 ret = CLAMP (ret, 0, 255); \
137 #define COMP_V(ret, r, g, b) \
139 ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
140 ret = CLAMP (ret, 0, 255); \
143 #define BLEND(ret, alpha, v0, v1) \
145 ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
148 #define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew) \
151 _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \
152 ret = CLAMP (_tmp, 0, 255); \
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
161 # define CAIRO_ARGB_A 0
162 # define CAIRO_ARGB_R 1
163 # define CAIRO_ARGB_G 2
164 # define CAIRO_ARGB_B 3
172 PROP_VALIGN, /* deprecated */
173 PROP_HALIGN, /* deprecated */
187 PROP_AUTO_ADJUST_SIZE,
188 PROP_VERTICAL_RENDER,
195 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
196 I420, YV12, AYUV, YUY2, UYVY, v308, v210, v216, Y41B, Y42B, Y444, \
197 Y800, Y16, NV12, NV21, UYVP, A420, YUV9, IYU1 }"
199 static GstStaticPadTemplate src_template_factory =
200 GST_STATIC_PAD_TEMPLATE ("src",
203 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
206 static GstStaticPadTemplate video_sink_template_factory =
207 GST_STATIC_PAD_TEMPLATE ("video_sink",
210 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
213 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
215 gst_base_text_overlay_valign_get_type (void)
217 static GType base_text_overlay_valign_type = 0;
218 static const GEnumValue base_text_overlay_valign[] = {
219 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
220 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
221 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
222 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
223 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
227 if (!base_text_overlay_valign_type) {
228 base_text_overlay_valign_type =
229 g_enum_register_static ("GstBaseTextOverlayVAlign",
230 base_text_overlay_valign);
232 return base_text_overlay_valign_type;
235 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
237 gst_base_text_overlay_halign_get_type (void)
239 static GType base_text_overlay_halign_type = 0;
240 static const GEnumValue base_text_overlay_halign[] = {
241 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
242 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
243 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
244 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
248 if (!base_text_overlay_halign_type) {
249 base_text_overlay_halign_type =
250 g_enum_register_static ("GstBaseTextOverlayHAlign",
251 base_text_overlay_halign);
253 return base_text_overlay_halign_type;
257 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
259 gst_base_text_overlay_wrap_mode_get_type (void)
261 static GType base_text_overlay_wrap_mode_type = 0;
262 static const GEnumValue base_text_overlay_wrap_mode[] = {
263 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
264 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
265 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
266 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
270 if (!base_text_overlay_wrap_mode_type) {
271 base_text_overlay_wrap_mode_type =
272 g_enum_register_static ("GstBaseTextOverlayWrapMode",
273 base_text_overlay_wrap_mode);
275 return base_text_overlay_wrap_mode_type;
278 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
280 gst_base_text_overlay_line_align_get_type (void)
282 static GType base_text_overlay_line_align_type = 0;
283 static const GEnumValue base_text_overlay_line_align[] = {
284 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
285 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
286 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
290 if (!base_text_overlay_line_align_type) {
291 base_text_overlay_line_align_type =
292 g_enum_register_static ("GstBaseTextOverlayLineAlign",
293 base_text_overlay_line_align);
295 return base_text_overlay_line_align_type;
298 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
299 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
300 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
301 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
302 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
303 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
304 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
306 static GstElementClass *parent_class = NULL;
307 static void gst_base_text_overlay_base_init (gpointer g_class);
308 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
309 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
310 GstBaseTextOverlayClass * klass);
312 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
313 element, GstStateChange transition);
315 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
316 GstBaseTextOverlay * overlay, GstCaps * filter);
317 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
319 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
321 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
322 GstObject * parent, GstEvent * event);
323 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
324 GstObject * parent, GstQuery * query);
326 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
327 GstObject * parent, GstEvent * event);
328 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
329 GstObject * parent, GstQuery * query);
330 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
331 GstObject * parent, GstBuffer * buffer);
333 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
334 GstObject * parent, GstEvent * event);
335 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
336 GstObject * parent, GstBuffer * buffer);
337 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
339 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad);
340 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
341 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
344 static void gst_base_text_overlay_finalize (GObject * object);
345 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
346 const GValue * value, GParamSpec * pspec);
347 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
348 GValue * value, GParamSpec * pspec);
350 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
351 PangoFontDescription * desc);
354 gst_base_text_overlay_get_type (void)
356 static GType type = 0;
358 if (g_once_init_enter ((gsize *) & type)) {
359 static const GTypeInfo info = {
360 sizeof (GstBaseTextOverlayClass),
361 (GBaseInitFunc) gst_base_text_overlay_base_init,
363 (GClassInitFunc) gst_base_text_overlay_class_init,
366 sizeof (GstBaseTextOverlay),
368 (GInstanceInitFunc) gst_base_text_overlay_init,
371 g_once_init_leave ((gsize *) & type,
372 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
380 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
381 GstBuffer * video_frame)
383 return g_strdup (overlay->default_text);
387 gst_base_text_overlay_base_init (gpointer g_class)
389 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
390 PangoFontMap *fontmap;
392 /* Only lock for the subclasses here, the base class
393 * doesn't have this mutex yet and it's not necessary
395 if (klass->pango_lock)
396 g_mutex_lock (klass->pango_lock);
397 fontmap = pango_cairo_font_map_get_default ();
398 klass->pango_context =
399 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
400 if (klass->pango_lock)
401 g_mutex_unlock (klass->pango_lock);
405 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
407 GObjectClass *gobject_class;
408 GstElementClass *gstelement_class;
410 gobject_class = (GObjectClass *) klass;
411 gstelement_class = (GstElementClass *) klass;
413 parent_class = g_type_class_peek_parent (klass);
415 gobject_class->finalize = gst_base_text_overlay_finalize;
416 gobject_class->set_property = gst_base_text_overlay_set_property;
417 gobject_class->get_property = gst_base_text_overlay_get_property;
419 gst_element_class_add_pad_template (gstelement_class,
420 gst_static_pad_template_get (&src_template_factory));
421 gst_element_class_add_pad_template (gstelement_class,
422 gst_static_pad_template_get (&video_sink_template_factory));
424 gstelement_class->change_state =
425 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
427 klass->pango_lock = g_slice_new (GMutex);
428 g_mutex_init (klass->pango_lock);
430 klass->get_text = gst_base_text_overlay_get_text;
432 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
433 g_param_spec_string ("text", "text",
434 "Text to be display.", DEFAULT_PROP_TEXT,
435 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
436 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
437 g_param_spec_boolean ("shaded-background", "shaded background",
438 "Whether to shade the background under the text area",
439 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
440 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
441 g_param_spec_enum ("valignment", "vertical alignment",
442 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
443 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
444 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
445 g_param_spec_enum ("halignment", "horizontal alignment",
446 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
447 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
448 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGN,
449 g_param_spec_string ("valign", "vertical alignment",
450 "Vertical alignment of the text (deprecated; use valignment)",
451 DEFAULT_PROP_VALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
452 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGN,
453 g_param_spec_string ("halign", "horizontal alignment",
454 "Horizontal alignment of the text (deprecated; use halignment)",
455 DEFAULT_PROP_HALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
456 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
457 g_param_spec_int ("xpad", "horizontal paddding",
458 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
459 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
460 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
461 g_param_spec_int ("ypad", "vertical padding",
462 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
463 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
464 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
465 g_param_spec_int ("deltax", "X position modifier",
466 "Shift X position to the left or to the right. Unit is pixels.",
467 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
468 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
469 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
470 g_param_spec_int ("deltay", "Y position modifier",
471 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
472 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
474 * GstBaseTextOverlay:xpos
476 * Horizontal position of the rendered text when using positioned alignment.
480 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
481 g_param_spec_double ("xpos", "horizontal position",
482 "Horizontal position when using position alignment", 0, 1.0,
484 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
486 * GstBaseTextOverlay:ypos
488 * Vertical position of the rendered text when using positioned alignment.
492 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
493 g_param_spec_double ("ypos", "vertical position",
494 "Vertical position when using position alignment", 0, 1.0,
496 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
497 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
498 g_param_spec_enum ("wrap-mode", "wrap mode",
499 "Whether to wrap the text and if so how.",
500 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
501 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
502 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
503 g_param_spec_string ("font-desc", "font description",
504 "Pango font description of font to be used for rendering. "
505 "See documentation of pango_font_description_from_string "
506 "for syntax.", DEFAULT_PROP_FONT_DESC,
507 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
509 * GstBaseTextOverlay:color
511 * Color of the rendered text.
515 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
516 g_param_spec_uint ("color", "Color",
517 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
519 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
521 * GstTextOverlay:outline-color
523 * Color of the outline of the rendered text.
527 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
528 g_param_spec_uint ("outline-color", "Text Outline Color",
529 "Color to use for outline the text (big-endian ARGB).", 0,
530 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
531 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
534 * GstBaseTextOverlay:line-alignment
536 * Alignment of text lines relative to each other (for multi-line text)
540 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
541 g_param_spec_enum ("line-alignment", "line alignment",
542 "Alignment of text lines relative to each other.",
543 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
544 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
546 * GstBaseTextOverlay:silent
548 * If set, no text is rendered. Useful to switch off text rendering
549 * temporarily without removing the textoverlay element from the pipeline.
553 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
554 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
555 g_param_spec_boolean ("silent", "silent",
556 "Whether to render the text string",
558 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
560 * GstBaseTextOverlay:wait-text
562 * If set, the video will block until a subtitle is received on the text pad.
563 * If video and subtitles are sent in sync, like from the same demuxer, this
564 * property should be set.
568 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
569 g_param_spec_boolean ("wait-text", "Wait Text",
570 "Whether to wait for subtitles",
571 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
573 g_object_class_install_property (G_OBJECT_CLASS (klass),
574 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
575 "Automatically adjust font size to screen-size.",
576 DEFAULT_PROP_AUTO_ADJUST_SIZE,
577 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
579 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
580 g_param_spec_boolean ("vertical-render", "vertical render",
581 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
582 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
586 gst_base_text_overlay_finalize (GObject * object)
588 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
590 g_free (overlay->default_text);
592 if (overlay->composition) {
593 gst_video_overlay_composition_unref (overlay->composition);
594 overlay->composition = NULL;
597 if (overlay->text_image) {
598 gst_buffer_unref (overlay->text_image);
599 overlay->text_image = NULL;
602 if (overlay->layout) {
603 g_object_unref (overlay->layout);
604 overlay->layout = NULL;
607 if (overlay->text_buffer) {
608 gst_buffer_unref (overlay->text_buffer);
609 overlay->text_buffer = NULL;
612 g_mutex_clear (&overlay->lock);
613 g_cond_clear (&overlay->cond);
615 G_OBJECT_CLASS (parent_class)->finalize (object);
619 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
620 GstBaseTextOverlayClass * klass)
622 GstPadTemplate *template;
623 PangoFontDescription *desc;
626 template = gst_static_pad_template_get (&video_sink_template_factory);
627 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
628 gst_object_unref (template);
629 gst_pad_set_event_function (overlay->video_sinkpad,
630 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
631 gst_pad_set_chain_function (overlay->video_sinkpad,
632 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
633 gst_pad_set_query_function (overlay->video_sinkpad,
634 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
635 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
638 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
642 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
643 gst_object_unref (template);
645 gst_pad_set_event_function (overlay->text_sinkpad,
646 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
647 gst_pad_set_chain_function (overlay->text_sinkpad,
648 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
649 gst_pad_set_link_function (overlay->text_sinkpad,
650 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
651 gst_pad_set_unlink_function (overlay->text_sinkpad,
652 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
653 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
657 template = gst_static_pad_template_get (&src_template_factory);
658 overlay->srcpad = gst_pad_new_from_template (template, "src");
659 gst_object_unref (template);
660 gst_pad_set_event_function (overlay->srcpad,
661 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
662 gst_pad_set_query_function (overlay->srcpad,
663 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
664 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
666 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
667 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
669 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
670 (overlay)->pango_context);
672 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
673 (overlay)->pango_context);
674 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
676 overlay->color = DEFAULT_PROP_COLOR;
677 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
678 overlay->halign = DEFAULT_PROP_HALIGNMENT;
679 overlay->valign = DEFAULT_PROP_VALIGNMENT;
680 overlay->xpad = DEFAULT_PROP_XPAD;
681 overlay->ypad = DEFAULT_PROP_YPAD;
682 overlay->deltax = DEFAULT_PROP_DELTAX;
683 overlay->deltay = DEFAULT_PROP_DELTAY;
684 overlay->xpos = DEFAULT_PROP_XPOS;
685 overlay->ypos = DEFAULT_PROP_YPOS;
687 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
689 overlay->want_shading = DEFAULT_PROP_SHADING;
690 overlay->shading_value = DEFAULT_SHADING_VALUE;
691 overlay->silent = DEFAULT_PROP_SILENT;
692 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
693 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
695 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
696 overlay->need_render = TRUE;
697 overlay->text_image = NULL;
698 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
699 gst_base_text_overlay_update_render_mode (overlay);
701 overlay->text_buffer = NULL;
702 overlay->text_linked = FALSE;
703 g_mutex_init (&overlay->lock);
704 g_cond_init (&overlay->cond);
705 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
706 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
710 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
712 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
713 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
714 pango_layout_set_width (overlay->layout, -1);
718 if (overlay->auto_adjust_size) {
719 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
720 if (overlay->use_vertical_render) {
721 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
725 (overlay->use_vertical_render ? overlay->height : overlay->width) *
729 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
730 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
731 pango_layout_set_width (overlay->layout, width);
732 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
737 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
739 PangoMatrix matrix = PANGO_MATRIX_INIT;
740 PangoContext *context = pango_layout_get_context (overlay->layout);
742 if (overlay->use_vertical_render) {
743 pango_matrix_rotate (&matrix, -90);
744 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
745 pango_context_set_matrix (context, &matrix);
746 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
748 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
749 pango_context_set_matrix (context, &matrix);
750 pango_layout_set_alignment (overlay->layout,
751 (PangoAlignment) overlay->line_align);
756 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
758 GstStructure *structure;
760 structure = gst_caps_get_structure (caps, 0);
761 overlay->have_pango_markup =
762 gst_structure_has_name (structure, "text/x-pango-markup");
767 /* FIXME: upstream nego (e.g. when the video window is resized) */
770 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
773 gboolean ret = FALSE;
775 if (!gst_video_info_from_caps (&info, caps))
778 overlay->info = info;
779 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
780 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
781 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
783 ret = gst_pad_set_caps (overlay->srcpad, caps);
787 GstStructure *structure;
790 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
791 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
793 /* FIXME Use the query to the sink to do that when implemented */
794 /* Update wether to attach composition to buffer or do the composition
797 structure = gst_caps_get_structure (caps, 0);
798 if (gst_structure_has_name (structure, "video/x-surface"))
799 overlay->attach_compo_to_buffer = TRUE;
801 overlay->attach_compo_to_buffer = FALSE;
803 GST_FIXME_OBJECT (overlay, "query downstream for overlay support");
804 overlay->attach_compo_to_buffer = FALSE;
807 gst_base_text_overlay_update_wrap_mode (overlay);
808 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
809 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
817 GST_DEBUG_OBJECT (overlay, "could not parse caps");
823 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
824 const GValue * value, GParamSpec * pspec)
826 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
828 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
831 g_free (overlay->default_text);
832 overlay->default_text = g_value_dup_string (value);
833 overlay->need_render = TRUE;
836 overlay->want_shading = g_value_get_boolean (value);
839 overlay->xpad = g_value_get_int (value);
842 overlay->ypad = g_value_get_int (value);
845 overlay->deltax = g_value_get_int (value);
848 overlay->deltay = g_value_get_int (value);
851 overlay->xpos = g_value_get_double (value);
854 overlay->ypos = g_value_get_double (value);
857 const gchar *s = g_value_get_string (value);
859 if (s && g_ascii_strcasecmp (s, "left") == 0)
860 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_LEFT;
861 else if (s && g_ascii_strcasecmp (s, "center") == 0)
862 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_CENTER;
863 else if (s && g_ascii_strcasecmp (s, "right") == 0)
864 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
866 g_warning ("Invalid value '%s' for textoverlay property 'halign'",
871 const gchar *s = g_value_get_string (value);
873 if (s && g_ascii_strcasecmp (s, "baseline") == 0)
874 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE;
875 else if (s && g_ascii_strcasecmp (s, "bottom") == 0)
876 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM;
877 else if (s && g_ascii_strcasecmp (s, "top") == 0)
878 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
880 g_warning ("Invalid value '%s' for textoverlay property 'valign'",
884 case PROP_VALIGNMENT:
885 overlay->valign = g_value_get_enum (value);
887 case PROP_HALIGNMENT:
888 overlay->halign = g_value_get_enum (value);
891 overlay->wrap_mode = g_value_get_enum (value);
892 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
893 gst_base_text_overlay_update_wrap_mode (overlay);
894 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
898 PangoFontDescription *desc;
899 const gchar *fontdesc_str;
901 fontdesc_str = g_value_get_string (value);
902 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
903 desc = pango_font_description_from_string (fontdesc_str);
905 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
906 pango_layout_set_font_description (overlay->layout, desc);
907 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
908 pango_font_description_free (desc);
910 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
913 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
917 overlay->color = g_value_get_uint (value);
919 case PROP_OUTLINE_COLOR:
920 overlay->outline_color = g_value_get_uint (value);
923 overlay->silent = g_value_get_boolean (value);
925 case PROP_LINE_ALIGNMENT:
926 overlay->line_align = g_value_get_enum (value);
927 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
928 pango_layout_set_alignment (overlay->layout,
929 (PangoAlignment) overlay->line_align);
930 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
933 overlay->wait_text = g_value_get_boolean (value);
935 case PROP_AUTO_ADJUST_SIZE:
936 overlay->auto_adjust_size = g_value_get_boolean (value);
937 overlay->need_render = TRUE;
939 case PROP_VERTICAL_RENDER:
940 overlay->use_vertical_render = g_value_get_boolean (value);
941 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
942 gst_base_text_overlay_update_render_mode (overlay);
943 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
944 overlay->need_render = TRUE;
947 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
951 overlay->need_render = TRUE;
952 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
956 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
957 GValue * value, GParamSpec * pspec)
959 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
961 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
964 g_value_set_string (value, overlay->default_text);
967 g_value_set_boolean (value, overlay->want_shading);
970 g_value_set_int (value, overlay->xpad);
973 g_value_set_int (value, overlay->ypad);
976 g_value_set_int (value, overlay->deltax);
979 g_value_set_int (value, overlay->deltay);
982 g_value_set_double (value, overlay->xpos);
985 g_value_set_double (value, overlay->ypos);
987 case PROP_VALIGNMENT:
988 g_value_set_enum (value, overlay->valign);
990 case PROP_HALIGNMENT:
991 g_value_set_enum (value, overlay->halign);
994 g_value_set_enum (value, overlay->wrap_mode);
997 g_value_set_boolean (value, overlay->silent);
999 case PROP_LINE_ALIGNMENT:
1000 g_value_set_enum (value, overlay->line_align);
1002 case PROP_WAIT_TEXT:
1003 g_value_set_boolean (value, overlay->wait_text);
1005 case PROP_AUTO_ADJUST_SIZE:
1006 g_value_set_boolean (value, overlay->auto_adjust_size);
1008 case PROP_VERTICAL_RENDER:
1009 g_value_set_boolean (value, overlay->use_vertical_render);
1012 g_value_set_uint (value, overlay->color);
1014 case PROP_OUTLINE_COLOR:
1015 g_value_set_uint (value, overlay->outline_color);
1018 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1022 overlay->need_render = TRUE;
1023 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1027 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1030 gboolean ret = FALSE;
1031 GstBaseTextOverlay *overlay;
1033 overlay = GST_BASE_TEXT_OVERLAY (parent);
1035 switch (GST_QUERY_TYPE (query)) {
1036 case GST_QUERY_CAPS:
1038 GstCaps *filter, *caps;
1040 gst_query_parse_caps (query, &filter);
1041 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1042 gst_query_set_caps_result (query, caps);
1043 gst_caps_unref (caps);
1048 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1056 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1059 gboolean ret = FALSE;
1060 GstBaseTextOverlay *overlay = NULL;
1062 overlay = GST_BASE_TEXT_OVERLAY (parent);
1064 switch (GST_EVENT_TYPE (event)) {
1065 case GST_EVENT_SEEK:{
1068 /* We don't handle seek if we have not text pad */
1069 if (!overlay->text_linked) {
1070 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1071 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1075 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1077 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1079 /* Flush downstream, only for flushing seek */
1080 if (flags & GST_SEEK_FLAG_FLUSH)
1081 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1083 /* Mark ourself as flushing, unblock chains */
1084 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1085 overlay->video_flushing = TRUE;
1086 overlay->text_flushing = TRUE;
1087 gst_base_text_overlay_pop_text (overlay);
1088 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1090 /* Seek on each sink pad */
1091 gst_event_ref (event);
1092 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1094 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1096 gst_event_unref (event);
1101 if (overlay->text_linked) {
1102 gst_event_ref (event);
1103 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1104 gst_pad_push_event (overlay->text_sinkpad, event);
1106 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1117 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1123 if (G_UNLIKELY (!overlay))
1124 return gst_pad_get_pad_template_caps (pad);
1126 if (pad == overlay->srcpad)
1127 otherpad = overlay->video_sinkpad;
1129 otherpad = overlay->srcpad;
1131 /* we can do what the peer can */
1132 caps = gst_pad_peer_query_caps (otherpad, filter);
1134 GstCaps *temp, *templ;
1136 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1138 /* filtered against our padtemplate */
1139 templ = gst_pad_get_pad_template_caps (otherpad);
1140 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1141 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1142 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1143 gst_caps_unref (caps);
1144 gst_caps_unref (templ);
1145 /* this is what we can do */
1148 /* no peer, our padtemplate is enough then */
1149 caps = gst_pad_get_pad_template_caps (pad);
1151 GstCaps *intersection;
1154 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1155 gst_caps_unref (caps);
1156 caps = intersection;
1160 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1166 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1167 PangoFontDescription * desc)
1169 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1170 overlay->shadow_offset = (double) (font_size) / 13.0;
1171 overlay->outline_offset = (double) (font_size) / 15.0;
1172 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1173 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1176 #define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \
1177 *b = (a > 0) ? MIN ((*b * 255 + a / 2) / a, 255) : 0; \
1178 *g = (a > 0) ? MIN ((*g * 255 + a / 2) / a, 255) : 0; \
1179 *r = (a > 0) ? MIN ((*r * 255 + a / 2) / a, 255) : 0; \
1183 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1184 gint * xpos, gint * ypos)
1187 GstBaseTextOverlayVAlign valign;
1188 GstBaseTextOverlayHAlign halign;
1190 width = overlay->image_width;
1191 height = overlay->image_height;
1193 if (overlay->use_vertical_render)
1194 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1196 halign = overlay->halign;
1199 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1200 *xpos = overlay->xpad;
1202 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1203 *xpos = (overlay->width - width) / 2;
1205 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1206 *xpos = overlay->width - width - overlay->xpad;
1208 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1209 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1210 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1217 *xpos += overlay->deltax;
1219 if (overlay->use_vertical_render)
1220 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1222 valign = overlay->valign;
1225 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1226 *ypos = overlay->height - height - overlay->ypad;
1228 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1229 *ypos = overlay->height - (height + overlay->ypad);
1231 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1232 *ypos = overlay->ypad;
1234 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1235 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1236 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1238 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1239 *ypos = (overlay->height - height) / 2;
1242 *ypos = overlay->ypad;
1245 *ypos += overlay->deltay;
1249 gst_base_text_overlay_unpremultiply (GstBaseTextOverlay * overlay)
1252 guint8 *pimage, *text_image;
1255 gst_buffer_map (overlay->text_image, &map, GST_MAP_READ);
1256 text_image = map.data;
1257 for (i = 0; i < overlay->image_height; i++) {
1258 pimage = text_image + 4 * (i * overlay->image_width);
1259 for (j = 0; j < overlay->image_width; j++) {
1260 CAIRO_UNPREMULTIPLY (pimage[CAIRO_ARGB_A], &pimage[CAIRO_ARGB_R],
1261 &pimage[CAIRO_ARGB_G], &pimage[CAIRO_ARGB_B]);
1266 gst_buffer_unmap (overlay->text_image, &map);
1270 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1273 GstVideoOverlayRectangle *rectangle;
1275 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1277 if (overlay->text_image) {
1278 rectangle = gst_video_overlay_rectangle_new_argb (overlay->text_image,
1279 overlay->image_width, overlay->image_height, 4 * overlay->image_width,
1280 xpos, ypos, overlay->image_width, overlay->image_height,
1281 GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE);
1283 if (overlay->composition)
1284 gst_video_overlay_composition_unref (overlay->composition);
1285 overlay->composition = gst_video_overlay_composition_new (rectangle);
1286 gst_video_overlay_rectangle_unref (rectangle);
1288 } else if (overlay->composition) {
1289 gst_video_overlay_composition_unref (overlay->composition);
1290 overlay->composition = NULL;
1295 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1296 const gchar * string, gint textlen)
1299 cairo_surface_t *surface;
1300 PangoRectangle ink_rect, logical_rect;
1301 cairo_matrix_t cairo_matrix;
1303 double scalef = 1.0;
1308 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1310 if (overlay->auto_adjust_size) {
1311 /* 640 pixel is default */
1312 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1314 pango_layout_set_width (overlay->layout, -1);
1315 /* set text on pango layout */
1316 pango_layout_set_markup (overlay->layout, string, textlen);
1318 /* get subtitle image size */
1319 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1321 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1323 if (width + overlay->deltax >
1324 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1326 * subtitle image width is larger then overlay width
1327 * so rearrange overlay wrap mode.
1329 gst_base_text_overlay_update_wrap_mode (overlay);
1330 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1331 width = overlay->width;
1335 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1336 if (height > overlay->height) {
1337 height = overlay->height;
1339 if (overlay->use_vertical_render) {
1340 PangoRectangle rect;
1341 PangoContext *context;
1342 PangoMatrix matrix = PANGO_MATRIX_INIT;
1345 context = pango_layout_get_context (overlay->layout);
1347 pango_matrix_rotate (&matrix, -90);
1349 rect.x = rect.y = 0;
1351 rect.height = height;
1352 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1353 matrix.x0 = -rect.x;
1354 matrix.y0 = -rect.y;
1356 pango_context_set_matrix (context, &matrix);
1358 cairo_matrix.xx = matrix.xx;
1359 cairo_matrix.yx = matrix.yx;
1360 cairo_matrix.xy = matrix.xy;
1361 cairo_matrix.yy = matrix.yy;
1362 cairo_matrix.x0 = matrix.x0;
1363 cairo_matrix.y0 = matrix.y0;
1364 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1370 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1373 /* reallocate overlay buffer */
1374 buffer = gst_buffer_new_and_alloc (4 * width * height);
1375 gst_buffer_replace (&overlay->text_image, buffer);
1376 gst_buffer_unref (buffer);
1378 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1379 surface = cairo_image_surface_create_for_data (map.data,
1380 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1381 cr = cairo_create (surface);
1384 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1387 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1389 if (overlay->want_shading)
1390 cairo_paint_with_alpha (cr, overlay->shading_value);
1392 /* apply transformations */
1393 cairo_set_matrix (cr, &cairo_matrix);
1395 /* FIXME: We use show_layout everywhere except for the surface
1396 * because it's really faster and internally does all kinds of
1397 * caching. Unfortunately we have to paint to a cairo path for
1398 * the outline and this is slow. Once Pango supports user fonts
1399 * we should use them, see
1400 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1402 * Idea would the be, to create a cairo user font that
1403 * does shadow, outline, text painting in the
1404 * render_glyph function.
1407 /* draw shadow text */
1409 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1410 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1411 pango_cairo_show_layout (cr, overlay->layout);
1414 a = (overlay->outline_color >> 24) & 0xff;
1415 r = (overlay->outline_color >> 16) & 0xff;
1416 g = (overlay->outline_color >> 8) & 0xff;
1417 b = (overlay->outline_color >> 0) & 0xff;
1419 /* draw outline text */
1421 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1422 cairo_set_line_width (cr, overlay->outline_offset);
1423 pango_cairo_layout_path (cr, overlay->layout);
1427 a = (overlay->color >> 24) & 0xff;
1428 r = (overlay->color >> 16) & 0xff;
1429 g = (overlay->color >> 8) & 0xff;
1430 b = (overlay->color >> 0) & 0xff;
1434 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1435 pango_cairo_show_layout (cr, overlay->layout);
1439 cairo_surface_destroy (surface);
1440 gst_buffer_unmap (buffer, &map);
1441 overlay->image_width = width;
1442 overlay->image_height = height;
1443 overlay->baseline_y = ink_rect.y;
1444 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1446 /* As the GstVideoOverlayComposition supports only unpremultiply ARGB,
1447 * we need to unpremultiply it */
1448 gst_base_text_overlay_unpremultiply (overlay);
1449 gst_base_text_overlay_set_composition (overlay);
1456 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1457 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1459 gint i, j, dest_stride;
1462 dest_stride = dest->info.stride[0];
1463 dest_ptr = dest->data[0];
1465 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1466 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1468 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1469 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1471 for (i = y0; i < y1; ++i) {
1472 for (j = x0; j < x1; ++j) {
1473 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1475 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1481 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1482 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1485 guint dest_stride, pixel_stride;
1488 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1489 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1490 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1492 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1493 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1495 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1496 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1499 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1501 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1504 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1506 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1508 for (i = y0; i < y1; i++) {
1509 for (j = x0; j < x1; j++) {
1513 y_pos = (i * dest_stride) + j * pixel_stride;
1514 y = dest_ptr[y_pos] + overlay->shading_value;
1516 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1521 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1522 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1523 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1525 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1526 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1531 dest_ptr = dest->data[0];
1533 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1534 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1536 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1537 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1539 for (i = y0; i < y1; i++) {
1540 for (j = x0; j < x1; j++) {
1543 y_pos = (i * 4 * overlay->width) + j * 4;
1544 for (k = 0; k < 4; k++) {
1545 y = dest_ptr[y_pos + k] + overlay->shading_value;
1546 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1552 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1553 static inline void \
1554 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1555 gint x0, gint x1, gint y0, gint y1) \
1560 dest_ptr = dest->data[0];\
1562 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1563 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1565 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1566 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1568 for (i = y0; i < y1; i++) {\
1569 for (j = x0; j < x1; j++) {\
1571 y_pos = (i * 4 * overlay->width) + j * 4;\
1572 for (k = OFFSET; k < 3+OFFSET; k++) {\
1573 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1574 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1579 ARGB_SHADE_FUNCTION (ARGB, 1);
1580 ARGB_SHADE_FUNCTION (ABGR, 1);
1581 ARGB_SHADE_FUNCTION (RGBA, 0);
1582 ARGB_SHADE_FUNCTION (BGRA, 0);
1585 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1586 const gchar * text, gint textlen)
1590 if (!overlay->need_render) {
1591 GST_DEBUG ("Using previously rendered text.");
1595 /* -1 is the whole string */
1596 if (text != NULL && textlen < 0) {
1597 textlen = strlen (text);
1601 string = g_strndup (text, textlen);
1602 } else { /* empty string */
1603 string = g_strdup (" ");
1605 g_strdelimit (string, "\r\t", ' ');
1606 textlen = strlen (string);
1608 /* FIXME: should we check for UTF-8 here? */
1610 GST_DEBUG ("Rendering '%s'", string);
1611 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1615 overlay->need_render = FALSE;
1618 static GstFlowReturn
1619 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1620 GstBuffer * video_frame)
1623 GstVideoFrame frame;
1625 video_frame = gst_buffer_make_writable (video_frame);
1627 if (!gst_video_frame_map (&frame, &overlay->info, video_frame, GST_MAP_WRITE))
1630 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1632 /* shaded background box */
1633 if (overlay->want_shading) {
1634 switch (overlay->format) {
1635 case GST_VIDEO_FORMAT_I420:
1636 case GST_VIDEO_FORMAT_NV12:
1637 case GST_VIDEO_FORMAT_NV21:
1638 gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1639 xpos, xpos + overlay->image_width,
1640 ypos, ypos + overlay->image_height);
1642 case GST_VIDEO_FORMAT_AYUV:
1643 case GST_VIDEO_FORMAT_UYVY:
1644 gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1645 xpos, xpos + overlay->image_width,
1646 ypos, ypos + overlay->image_height);
1648 case GST_VIDEO_FORMAT_xRGB:
1649 gst_base_text_overlay_shade_xRGB (overlay, &frame,
1650 xpos, xpos + overlay->image_width,
1651 ypos, ypos + overlay->image_height);
1653 case GST_VIDEO_FORMAT_xBGR:
1654 gst_base_text_overlay_shade_xBGR (overlay, &frame,
1655 xpos, xpos + overlay->image_width,
1656 ypos, ypos + overlay->image_height);
1658 case GST_VIDEO_FORMAT_BGRx:
1659 gst_base_text_overlay_shade_BGRx (overlay, &frame,
1660 xpos, xpos + overlay->image_width,
1661 ypos, ypos + overlay->image_height);
1663 case GST_VIDEO_FORMAT_RGBx:
1664 gst_base_text_overlay_shade_RGBx (overlay, &frame,
1665 xpos, xpos + overlay->image_width,
1666 ypos, ypos + overlay->image_height);
1668 case GST_VIDEO_FORMAT_ARGB:
1669 gst_base_text_overlay_shade_ARGB (overlay, &frame,
1670 xpos, xpos + overlay->image_width,
1671 ypos, ypos + overlay->image_height);
1673 case GST_VIDEO_FORMAT_ABGR:
1674 gst_base_text_overlay_shade_ABGR (overlay, &frame,
1675 xpos, xpos + overlay->image_width,
1676 ypos, ypos + overlay->image_height);
1678 case GST_VIDEO_FORMAT_RGBA:
1679 gst_base_text_overlay_shade_RGBA (overlay, &frame,
1680 xpos, xpos + overlay->image_width,
1681 ypos, ypos + overlay->image_height);
1683 case GST_VIDEO_FORMAT_BGRA:
1684 gst_base_text_overlay_shade_BGRA (overlay, &frame,
1685 xpos, xpos + overlay->image_width,
1686 ypos, ypos + overlay->image_height);
1689 g_assert_not_reached ();
1693 if (overlay->composition) {
1694 if (overlay->attach_compo_to_buffer) {
1695 GST_DEBUG_OBJECT (overlay, "Attaching text to the buffer");
1696 gst_video_buffer_set_overlay_composition (video_frame,
1697 overlay->composition);
1699 gst_video_overlay_composition_blend (overlay->composition, &frame);
1703 gst_video_frame_unmap (&frame);
1705 return gst_pad_push (overlay->srcpad, video_frame);
1710 gst_buffer_unref (video_frame);
1711 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1716 static GstPadLinkReturn
1717 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1719 GstBaseTextOverlay *overlay;
1721 overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1722 if (G_UNLIKELY (!overlay))
1723 return GST_PAD_LINK_REFUSED;
1725 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1727 overlay->text_linked = TRUE;
1729 gst_object_unref (overlay);
1731 return GST_PAD_LINK_OK;
1735 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
1737 GstBaseTextOverlay *overlay;
1739 /* don't use gst_pad_get_parent() here, will deadlock */
1740 overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1742 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1744 overlay->text_linked = FALSE;
1746 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1750 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1753 gboolean ret = FALSE;
1754 GstBaseTextOverlay *overlay = NULL;
1756 overlay = GST_BASE_TEXT_OVERLAY (parent);
1758 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1760 switch (GST_EVENT_TYPE (event)) {
1761 case GST_EVENT_CAPS:
1765 gst_event_parse_caps (event, &caps);
1766 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1767 gst_event_unref (event);
1770 case GST_EVENT_SEGMENT:
1772 const GstSegment *segment;
1774 overlay->text_eos = FALSE;
1776 gst_event_parse_segment (event, &segment);
1778 if (segment->format == GST_FORMAT_TIME) {
1779 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1780 gst_segment_copy_into (segment, &overlay->text_segment);
1781 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1782 &overlay->text_segment);
1783 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1785 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1786 ("received non-TIME newsegment event on text input"));
1789 gst_event_unref (event);
1792 /* wake up the video chain, it might be waiting for a text buffer or
1793 * a text segment update */
1794 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1795 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1796 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1799 case GST_EVENT_FLUSH_STOP:
1800 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1801 GST_INFO_OBJECT (overlay, "text flush stop");
1802 overlay->text_flushing = FALSE;
1803 overlay->text_eos = FALSE;
1804 gst_base_text_overlay_pop_text (overlay);
1805 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1806 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1807 gst_event_unref (event);
1810 case GST_EVENT_FLUSH_START:
1811 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1812 GST_INFO_OBJECT (overlay, "text flush start");
1813 overlay->text_flushing = TRUE;
1814 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1815 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1816 gst_event_unref (event);
1820 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1821 overlay->text_eos = TRUE;
1822 GST_INFO_OBJECT (overlay, "text EOS");
1823 /* wake up the video chain, it might be waiting for a text buffer or
1824 * a text segment update */
1825 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1826 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1827 gst_event_unref (event);
1831 ret = gst_pad_event_default (pad, parent, event);
1839 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1842 gboolean ret = FALSE;
1843 GstBaseTextOverlay *overlay = NULL;
1845 overlay = GST_BASE_TEXT_OVERLAY (parent);
1847 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1849 switch (GST_EVENT_TYPE (event)) {
1850 case GST_EVENT_CAPS:
1854 gst_event_parse_caps (event, &caps);
1855 ret = gst_base_text_overlay_setcaps (overlay, caps);
1856 gst_event_unref (event);
1859 case GST_EVENT_SEGMENT:
1861 const GstSegment *segment;
1863 GST_DEBUG_OBJECT (overlay, "received new segment");
1865 gst_event_parse_segment (event, &segment);
1867 if (segment->format == GST_FORMAT_TIME) {
1868 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1871 gst_segment_copy_into (segment, &overlay->segment);
1873 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1874 ("received non-TIME newsegment event on video input"));
1877 ret = gst_pad_event_default (pad, parent, event);
1881 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1882 GST_INFO_OBJECT (overlay, "video EOS");
1883 overlay->video_eos = TRUE;
1884 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1885 ret = gst_pad_event_default (pad, parent, event);
1887 case GST_EVENT_FLUSH_START:
1888 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1889 GST_INFO_OBJECT (overlay, "video flush start");
1890 overlay->video_flushing = TRUE;
1891 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1892 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1893 ret = gst_pad_event_default (pad, parent, event);
1895 case GST_EVENT_FLUSH_STOP:
1896 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1897 GST_INFO_OBJECT (overlay, "video flush stop");
1898 overlay->video_flushing = FALSE;
1899 overlay->video_eos = FALSE;
1900 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1901 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1902 ret = gst_pad_event_default (pad, parent, event);
1905 ret = gst_pad_event_default (pad, parent, event);
1913 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1916 gboolean ret = FALSE;
1917 GstBaseTextOverlay *overlay;
1919 overlay = GST_BASE_TEXT_OVERLAY (parent);
1921 switch (GST_QUERY_TYPE (query)) {
1922 case GST_QUERY_CAPS:
1924 GstCaps *filter, *caps;
1926 gst_query_parse_caps (query, &filter);
1927 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1928 gst_query_set_caps_result (query, caps);
1929 gst_caps_unref (caps);
1934 ret = gst_pad_query_default (pad, parent, query);
1941 /* Called with lock held */
1943 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1945 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1947 if (overlay->text_buffer) {
1948 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1949 overlay->text_buffer);
1950 gst_buffer_unref (overlay->text_buffer);
1951 overlay->text_buffer = NULL;
1954 /* Let the text task know we used that buffer */
1955 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1958 /* We receive text buffers here. If they are out of segment we just ignore them.
1959 If the buffer is in our segment we keep it internally except if another one
1960 is already waiting here, in that case we wait that it gets kicked out */
1961 static GstFlowReturn
1962 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1965 GstFlowReturn ret = GST_FLOW_OK;
1966 GstBaseTextOverlay *overlay = NULL;
1967 gboolean in_seg = FALSE;
1968 guint64 clip_start = 0, clip_stop = 0;
1970 overlay = GST_BASE_TEXT_OVERLAY (parent);
1972 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1974 if (overlay->text_flushing) {
1975 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1976 ret = GST_FLOW_FLUSHING;
1977 GST_LOG_OBJECT (overlay, "text flushing");
1981 if (overlay->text_eos) {
1982 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1984 GST_LOG_OBJECT (overlay, "text EOS");
1988 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1989 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1990 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1991 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1992 GST_BUFFER_DURATION (buffer)));
1994 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1997 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1998 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2000 stop = GST_CLOCK_TIME_NONE;
2002 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2003 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2009 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2010 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2011 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2012 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2014 /* Wait for the previous buffer to go away */
2015 while (overlay->text_buffer != NULL) {
2016 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2017 GST_DEBUG_PAD_NAME (pad));
2018 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2019 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2020 if (overlay->text_flushing) {
2021 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2022 ret = GST_FLOW_FLUSHING;
2027 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2028 overlay->text_segment.position = clip_start;
2030 overlay->text_buffer = buffer;
2031 /* That's a new text buffer we need to render */
2032 overlay->need_render = TRUE;
2034 /* in case the video chain is waiting for a text buffer, wake it up */
2035 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2038 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2045 static GstFlowReturn
2046 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2049 GstBaseTextOverlayClass *klass;
2050 GstBaseTextOverlay *overlay;
2051 GstFlowReturn ret = GST_FLOW_OK;
2052 gboolean in_seg = FALSE;
2053 guint64 start, stop, clip_start = 0, clip_stop = 0;
2056 overlay = GST_BASE_TEXT_OVERLAY (parent);
2057 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2059 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2060 goto missing_timestamp;
2062 /* ignore buffers that are outside of the current segment */
2063 start = GST_BUFFER_TIMESTAMP (buffer);
2065 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2066 stop = GST_CLOCK_TIME_NONE;
2068 stop = start + GST_BUFFER_DURATION (buffer);
2071 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2072 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2073 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2075 /* segment_clip() will adjust start unconditionally to segment_start if
2076 * no stop time is provided, so handle this ourselves */
2077 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2078 goto out_of_segment;
2080 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2081 &clip_start, &clip_stop);
2084 goto out_of_segment;
2086 /* if the buffer is only partially in the segment, fix up stamps */
2087 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2088 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2089 buffer = gst_buffer_make_writable (buffer);
2090 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2092 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2095 /* now, after we've done the clipping, fix up end time if there's no
2096 * duration (we only use those estimated values internally though, we
2097 * don't want to set bogus values on the buffer itself) */
2101 gint fps_num, fps_denom;
2103 /* FIXME, store this in setcaps */
2104 caps = gst_pad_get_current_caps (pad);
2105 s = gst_caps_get_structure (caps, 0);
2106 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2107 fps_num && fps_denom) {
2108 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2109 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2111 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2112 stop = start + 1; /* we need to assume some interval */
2114 gst_caps_unref (caps);
2117 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2121 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2123 if (overlay->video_flushing)
2126 if (overlay->video_eos)
2129 if (overlay->silent) {
2130 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2131 ret = gst_pad_push (overlay->srcpad, buffer);
2133 /* Update position */
2134 overlay->segment.position = clip_start;
2139 /* Text pad not linked, rendering internal text */
2140 if (!overlay->text_linked) {
2141 if (klass->get_text) {
2142 text = klass->get_text (overlay, buffer);
2144 text = g_strdup (overlay->default_text);
2147 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2148 "text: '%s'", GST_STR_NULL (text));
2150 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2152 if (text != NULL && *text != '\0') {
2153 /* Render and push */
2154 gst_base_text_overlay_render_text (overlay, text, -1);
2155 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2157 /* Invalid or empty string */
2158 ret = gst_pad_push (overlay->srcpad, buffer);
2161 /* Text pad linked, check if we have a text buffer queued */
2162 if (overlay->text_buffer) {
2163 gboolean pop_text = FALSE, valid_text_time = TRUE;
2164 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2165 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2166 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2167 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2168 GstClockTime vid_running_time, vid_running_time_end;
2170 /* if the text buffer isn't stamped right, pop it off the
2171 * queue and display it for the current video frame only */
2172 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2173 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2174 GST_WARNING_OBJECT (overlay,
2175 "Got text buffer with invalid timestamp or duration");
2177 valid_text_time = FALSE;
2179 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2180 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2184 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2186 vid_running_time_end =
2187 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2190 /* If timestamp and duration are valid */
2191 if (valid_text_time) {
2193 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2195 text_running_time_end =
2196 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2200 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2201 GST_TIME_ARGS (text_running_time),
2202 GST_TIME_ARGS (text_running_time_end));
2203 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2204 GST_TIME_ARGS (vid_running_time),
2205 GST_TIME_ARGS (vid_running_time_end));
2207 /* Text too old or in the future */
2208 if (valid_text_time && text_running_time_end <= vid_running_time) {
2209 /* text buffer too old, get rid of it and do nothing */
2210 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2212 gst_base_text_overlay_pop_text (overlay);
2213 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2214 goto wait_for_text_buf;
2215 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2216 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2217 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2218 /* Push the video frame */
2219 ret = gst_pad_push (overlay->srcpad, buffer);
2225 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2226 in_text = (gchar *) map.data;
2229 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2230 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2231 * here on purpose, this is something that needs fixing upstream */
2232 if (!g_utf8_validate (in_text, in_size, NULL)) {
2233 const gchar *end = NULL;
2235 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2236 in_text = g_strndup (in_text, in_size);
2237 while (!g_utf8_validate (in_text, in_size, &end) && end)
2238 *((gchar *) end) = '*';
2241 /* Get the string */
2242 if (overlay->have_pango_markup) {
2243 text = g_strndup (in_text, in_size);
2245 text = g_markup_escape_text (in_text, in_size);
2248 if (text != NULL && *text != '\0') {
2249 gint text_len = strlen (text);
2251 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2252 text[text_len - 1] == '\r')) {
2255 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2256 gst_base_text_overlay_render_text (overlay, text, text_len);
2258 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2259 gst_base_text_overlay_render_text (overlay, " ", 1);
2261 if (in_text != (gchar *) map.data)
2264 gst_buffer_unmap (overlay->text_buffer, &map);
2266 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2267 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2269 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2270 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2275 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2276 gst_base_text_overlay_pop_text (overlay);
2277 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2280 gboolean wait_for_text_buf = TRUE;
2282 if (overlay->text_eos)
2283 wait_for_text_buf = FALSE;
2285 if (!overlay->wait_text)
2286 wait_for_text_buf = FALSE;
2288 /* Text pad linked, but no text buffer available - what now? */
2289 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2290 GstClockTime text_start_running_time, text_position_running_time;
2291 GstClockTime vid_running_time;
2294 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2295 GST_BUFFER_TIMESTAMP (buffer));
2296 text_start_running_time =
2297 gst_segment_to_running_time (&overlay->text_segment,
2298 GST_FORMAT_TIME, overlay->text_segment.start);
2299 text_position_running_time =
2300 gst_segment_to_running_time (&overlay->text_segment,
2301 GST_FORMAT_TIME, overlay->text_segment.position);
2303 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2304 vid_running_time < text_start_running_time) ||
2305 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2306 vid_running_time < text_position_running_time)) {
2307 wait_for_text_buf = FALSE;
2311 if (wait_for_text_buf) {
2312 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2313 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2314 GST_DEBUG_OBJECT (overlay, "resuming");
2315 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2316 goto wait_for_text_buf;
2318 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2319 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2320 ret = gst_pad_push (overlay->srcpad, buffer);
2327 /* Update position */
2328 overlay->segment.position = clip_start;
2334 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2335 gst_buffer_unref (buffer);
2341 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2342 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2343 gst_buffer_unref (buffer);
2344 return GST_FLOW_FLUSHING;
2348 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2349 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2350 gst_buffer_unref (buffer);
2351 return GST_FLOW_EOS;
2355 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2356 gst_buffer_unref (buffer);
2361 static GstStateChangeReturn
2362 gst_base_text_overlay_change_state (GstElement * element,
2363 GstStateChange transition)
2365 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2366 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2368 switch (transition) {
2369 case GST_STATE_CHANGE_PAUSED_TO_READY:
2370 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2371 overlay->text_flushing = TRUE;
2372 overlay->video_flushing = TRUE;
2373 /* pop_text will broadcast on the GCond and thus also make the video
2374 * chain exit if it's waiting for a text buffer */
2375 gst_base_text_overlay_pop_text (overlay);
2376 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2382 ret = parent_class->change_state (element, transition);
2383 if (ret == GST_STATE_CHANGE_FAILURE)
2386 switch (transition) {
2387 case GST_STATE_CHANGE_READY_TO_PAUSED:
2388 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2389 overlay->text_flushing = FALSE;
2390 overlay->video_flushing = FALSE;
2391 overlay->video_eos = FALSE;
2392 overlay->text_eos = FALSE;
2393 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2394 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2395 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2405 plugin_init (GstPlugin * plugin)
2407 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2408 GST_TYPE_TEXT_OVERLAY) ||
2409 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2410 GST_TYPE_TIME_OVERLAY) ||
2411 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2412 GST_TYPE_CLOCK_OVERLAY) ||
2413 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2414 GST_TYPE_TEXT_RENDER)) {
2418 /*texttestsrc_plugin_init(module, plugin); */
2420 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2425 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2426 pango, "Pango-based text rendering and overlay", plugin_init,
2427 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)