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) */
769 /* only negotiate/query video overlay composition support for now */
771 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
775 gboolean attach = FALSE;
777 GST_DEBUG_OBJECT (overlay, "performing negotiation");
779 target = gst_pad_get_current_caps (overlay->srcpad);
781 if (!target || gst_caps_is_empty (target))
784 /* find supported meta */
785 query = gst_query_new_allocation (target, TRUE);
787 if (!gst_pad_peer_query (overlay->srcpad, query)) {
788 /* no problem, we use the query defaults */
789 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
792 if (gst_query_find_allocation_meta (query,
793 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
796 overlay->attach_compo_to_buffer = attach;
798 gst_query_unref (query);
799 gst_caps_unref (target);
806 gst_caps_unref (target);
812 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
815 gboolean ret = FALSE;
817 if (!gst_video_info_from_caps (&info, caps))
820 overlay->info = info;
821 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
822 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
823 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
825 ret = gst_pad_set_caps (overlay->srcpad, caps);
828 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
829 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
830 gst_base_text_overlay_negotiate (overlay);
831 gst_base_text_overlay_update_wrap_mode (overlay);
832 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
833 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
841 GST_DEBUG_OBJECT (overlay, "could not parse caps");
847 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
848 const GValue * value, GParamSpec * pspec)
850 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
852 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
855 g_free (overlay->default_text);
856 overlay->default_text = g_value_dup_string (value);
857 overlay->need_render = TRUE;
860 overlay->want_shading = g_value_get_boolean (value);
863 overlay->xpad = g_value_get_int (value);
866 overlay->ypad = g_value_get_int (value);
869 overlay->deltax = g_value_get_int (value);
872 overlay->deltay = g_value_get_int (value);
875 overlay->xpos = g_value_get_double (value);
878 overlay->ypos = g_value_get_double (value);
881 const gchar *s = g_value_get_string (value);
883 if (s && g_ascii_strcasecmp (s, "left") == 0)
884 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_LEFT;
885 else if (s && g_ascii_strcasecmp (s, "center") == 0)
886 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_CENTER;
887 else if (s && g_ascii_strcasecmp (s, "right") == 0)
888 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
890 g_warning ("Invalid value '%s' for textoverlay property 'halign'",
895 const gchar *s = g_value_get_string (value);
897 if (s && g_ascii_strcasecmp (s, "baseline") == 0)
898 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE;
899 else if (s && g_ascii_strcasecmp (s, "bottom") == 0)
900 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM;
901 else if (s && g_ascii_strcasecmp (s, "top") == 0)
902 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
904 g_warning ("Invalid value '%s' for textoverlay property 'valign'",
908 case PROP_VALIGNMENT:
909 overlay->valign = g_value_get_enum (value);
911 case PROP_HALIGNMENT:
912 overlay->halign = g_value_get_enum (value);
915 overlay->wrap_mode = g_value_get_enum (value);
916 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
917 gst_base_text_overlay_update_wrap_mode (overlay);
918 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
922 PangoFontDescription *desc;
923 const gchar *fontdesc_str;
925 fontdesc_str = g_value_get_string (value);
926 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
927 desc = pango_font_description_from_string (fontdesc_str);
929 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
930 pango_layout_set_font_description (overlay->layout, desc);
931 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
932 pango_font_description_free (desc);
934 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
937 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
941 overlay->color = g_value_get_uint (value);
943 case PROP_OUTLINE_COLOR:
944 overlay->outline_color = g_value_get_uint (value);
947 overlay->silent = g_value_get_boolean (value);
949 case PROP_LINE_ALIGNMENT:
950 overlay->line_align = g_value_get_enum (value);
951 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
952 pango_layout_set_alignment (overlay->layout,
953 (PangoAlignment) overlay->line_align);
954 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
957 overlay->wait_text = g_value_get_boolean (value);
959 case PROP_AUTO_ADJUST_SIZE:
960 overlay->auto_adjust_size = g_value_get_boolean (value);
961 overlay->need_render = TRUE;
963 case PROP_VERTICAL_RENDER:
964 overlay->use_vertical_render = g_value_get_boolean (value);
965 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
966 gst_base_text_overlay_update_render_mode (overlay);
967 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
968 overlay->need_render = TRUE;
971 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
975 overlay->need_render = TRUE;
976 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
980 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
981 GValue * value, GParamSpec * pspec)
983 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
985 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
988 g_value_set_string (value, overlay->default_text);
991 g_value_set_boolean (value, overlay->want_shading);
994 g_value_set_int (value, overlay->xpad);
997 g_value_set_int (value, overlay->ypad);
1000 g_value_set_int (value, overlay->deltax);
1003 g_value_set_int (value, overlay->deltay);
1006 g_value_set_double (value, overlay->xpos);
1009 g_value_set_double (value, overlay->ypos);
1011 case PROP_VALIGNMENT:
1012 g_value_set_enum (value, overlay->valign);
1014 case PROP_HALIGNMENT:
1015 g_value_set_enum (value, overlay->halign);
1017 case PROP_WRAP_MODE:
1018 g_value_set_enum (value, overlay->wrap_mode);
1021 g_value_set_boolean (value, overlay->silent);
1023 case PROP_LINE_ALIGNMENT:
1024 g_value_set_enum (value, overlay->line_align);
1026 case PROP_WAIT_TEXT:
1027 g_value_set_boolean (value, overlay->wait_text);
1029 case PROP_AUTO_ADJUST_SIZE:
1030 g_value_set_boolean (value, overlay->auto_adjust_size);
1032 case PROP_VERTICAL_RENDER:
1033 g_value_set_boolean (value, overlay->use_vertical_render);
1036 g_value_set_uint (value, overlay->color);
1038 case PROP_OUTLINE_COLOR:
1039 g_value_set_uint (value, overlay->outline_color);
1042 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1046 overlay->need_render = TRUE;
1047 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1051 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1054 gboolean ret = FALSE;
1055 GstBaseTextOverlay *overlay;
1057 overlay = GST_BASE_TEXT_OVERLAY (parent);
1059 switch (GST_QUERY_TYPE (query)) {
1060 case GST_QUERY_CAPS:
1062 GstCaps *filter, *caps;
1064 gst_query_parse_caps (query, &filter);
1065 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1066 gst_query_set_caps_result (query, caps);
1067 gst_caps_unref (caps);
1072 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1080 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1083 gboolean ret = FALSE;
1084 GstBaseTextOverlay *overlay = NULL;
1086 overlay = GST_BASE_TEXT_OVERLAY (parent);
1088 switch (GST_EVENT_TYPE (event)) {
1089 case GST_EVENT_SEEK:{
1092 /* We don't handle seek if we have not text pad */
1093 if (!overlay->text_linked) {
1094 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1095 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1099 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1101 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1103 /* Flush downstream, only for flushing seek */
1104 if (flags & GST_SEEK_FLAG_FLUSH)
1105 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1107 /* Mark ourself as flushing, unblock chains */
1108 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1109 overlay->video_flushing = TRUE;
1110 overlay->text_flushing = TRUE;
1111 gst_base_text_overlay_pop_text (overlay);
1112 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1114 /* Seek on each sink pad */
1115 gst_event_ref (event);
1116 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1118 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1120 gst_event_unref (event);
1125 if (overlay->text_linked) {
1126 gst_event_ref (event);
1127 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1128 gst_pad_push_event (overlay->text_sinkpad, event);
1130 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1141 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1147 if (G_UNLIKELY (!overlay))
1148 return gst_pad_get_pad_template_caps (pad);
1150 if (pad == overlay->srcpad)
1151 otherpad = overlay->video_sinkpad;
1153 otherpad = overlay->srcpad;
1155 /* we can do what the peer can */
1156 caps = gst_pad_peer_query_caps (otherpad, filter);
1158 GstCaps *temp, *templ;
1160 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1162 /* filtered against our padtemplate */
1163 templ = gst_pad_get_pad_template_caps (otherpad);
1164 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1165 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1166 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1167 gst_caps_unref (caps);
1168 gst_caps_unref (templ);
1169 /* this is what we can do */
1172 /* no peer, our padtemplate is enough then */
1173 caps = gst_pad_get_pad_template_caps (pad);
1175 GstCaps *intersection;
1178 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1179 gst_caps_unref (caps);
1180 caps = intersection;
1184 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1190 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1191 PangoFontDescription * desc)
1193 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1194 overlay->shadow_offset = (double) (font_size) / 13.0;
1195 overlay->outline_offset = (double) (font_size) / 15.0;
1196 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1197 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1201 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1202 gint * xpos, gint * ypos)
1205 GstBaseTextOverlayVAlign valign;
1206 GstBaseTextOverlayHAlign halign;
1208 width = overlay->image_width;
1209 height = overlay->image_height;
1211 if (overlay->use_vertical_render)
1212 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1214 halign = overlay->halign;
1217 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1218 *xpos = overlay->xpad;
1220 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1221 *xpos = (overlay->width - width) / 2;
1223 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1224 *xpos = overlay->width - width - overlay->xpad;
1226 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1227 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1228 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1235 *xpos += overlay->deltax;
1237 if (overlay->use_vertical_render)
1238 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1240 valign = overlay->valign;
1243 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1244 *ypos = overlay->height - height - overlay->ypad;
1246 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1247 *ypos = overlay->height - (height + overlay->ypad);
1249 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1250 *ypos = overlay->ypad;
1252 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1253 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1254 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1256 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1257 *ypos = (overlay->height - height) / 2;
1260 *ypos = overlay->ypad;
1263 *ypos += overlay->deltay;
1267 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1270 GstVideoOverlayRectangle *rectangle;
1272 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1274 if (overlay->text_image) {
1275 rectangle = gst_video_overlay_rectangle_new_argb (overlay->text_image,
1276 overlay->image_width, overlay->image_height, 4 * overlay->image_width,
1277 xpos, ypos, overlay->image_width, overlay->image_height,
1278 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1280 if (overlay->composition)
1281 gst_video_overlay_composition_unref (overlay->composition);
1282 overlay->composition = gst_video_overlay_composition_new (rectangle);
1283 gst_video_overlay_rectangle_unref (rectangle);
1285 } else if (overlay->composition) {
1286 gst_video_overlay_composition_unref (overlay->composition);
1287 overlay->composition = NULL;
1292 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1293 const gchar * string, gint textlen)
1296 cairo_surface_t *surface;
1297 PangoRectangle ink_rect, logical_rect;
1298 cairo_matrix_t cairo_matrix;
1300 double scalef = 1.0;
1305 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1307 if (overlay->auto_adjust_size) {
1308 /* 640 pixel is default */
1309 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1311 pango_layout_set_width (overlay->layout, -1);
1312 /* set text on pango layout */
1313 pango_layout_set_markup (overlay->layout, string, textlen);
1315 /* get subtitle image size */
1316 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1318 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1320 if (width + overlay->deltax >
1321 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1323 * subtitle image width is larger then overlay width
1324 * so rearrange overlay wrap mode.
1326 gst_base_text_overlay_update_wrap_mode (overlay);
1327 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1328 width = overlay->width;
1332 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1333 if (height > overlay->height) {
1334 height = overlay->height;
1336 if (overlay->use_vertical_render) {
1337 PangoRectangle rect;
1338 PangoContext *context;
1339 PangoMatrix matrix = PANGO_MATRIX_INIT;
1342 context = pango_layout_get_context (overlay->layout);
1344 pango_matrix_rotate (&matrix, -90);
1346 rect.x = rect.y = 0;
1348 rect.height = height;
1349 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1350 matrix.x0 = -rect.x;
1351 matrix.y0 = -rect.y;
1353 pango_context_set_matrix (context, &matrix);
1355 cairo_matrix.xx = matrix.xx;
1356 cairo_matrix.yx = matrix.yx;
1357 cairo_matrix.xy = matrix.xy;
1358 cairo_matrix.yy = matrix.yy;
1359 cairo_matrix.x0 = matrix.x0;
1360 cairo_matrix.y0 = matrix.y0;
1361 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1367 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1370 /* reallocate overlay buffer */
1371 buffer = gst_buffer_new_and_alloc (4 * width * height);
1372 gst_buffer_replace (&overlay->text_image, buffer);
1373 gst_buffer_unref (buffer);
1375 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1376 surface = cairo_image_surface_create_for_data (map.data,
1377 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1378 cr = cairo_create (surface);
1381 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1384 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1386 if (overlay->want_shading)
1387 cairo_paint_with_alpha (cr, overlay->shading_value);
1389 /* apply transformations */
1390 cairo_set_matrix (cr, &cairo_matrix);
1392 /* FIXME: We use show_layout everywhere except for the surface
1393 * because it's really faster and internally does all kinds of
1394 * caching. Unfortunately we have to paint to a cairo path for
1395 * the outline and this is slow. Once Pango supports user fonts
1396 * we should use them, see
1397 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1399 * Idea would the be, to create a cairo user font that
1400 * does shadow, outline, text painting in the
1401 * render_glyph function.
1404 /* draw shadow text */
1406 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1407 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1408 pango_cairo_show_layout (cr, overlay->layout);
1411 a = (overlay->outline_color >> 24) & 0xff;
1412 r = (overlay->outline_color >> 16) & 0xff;
1413 g = (overlay->outline_color >> 8) & 0xff;
1414 b = (overlay->outline_color >> 0) & 0xff;
1416 /* draw outline text */
1418 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1419 cairo_set_line_width (cr, overlay->outline_offset);
1420 pango_cairo_layout_path (cr, overlay->layout);
1424 a = (overlay->color >> 24) & 0xff;
1425 r = (overlay->color >> 16) & 0xff;
1426 g = (overlay->color >> 8) & 0xff;
1427 b = (overlay->color >> 0) & 0xff;
1431 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1432 pango_cairo_show_layout (cr, overlay->layout);
1436 cairo_surface_destroy (surface);
1437 gst_buffer_unmap (buffer, &map);
1438 overlay->image_width = width;
1439 overlay->image_height = height;
1440 overlay->baseline_y = ink_rect.y;
1441 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1443 gst_base_text_overlay_set_composition (overlay);
1450 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1451 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1453 gint i, j, dest_stride;
1456 dest_stride = dest->info.stride[0];
1457 dest_ptr = dest->data[0];
1459 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1460 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1462 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1463 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1465 for (i = y0; i < y1; ++i) {
1466 for (j = x0; j < x1; ++j) {
1467 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1469 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1475 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1476 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1479 guint dest_stride, pixel_stride;
1482 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1483 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1484 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1486 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1487 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1489 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1490 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1493 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1495 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1498 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1500 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1502 for (i = y0; i < y1; i++) {
1503 for (j = x0; j < x1; j++) {
1507 y_pos = (i * dest_stride) + j * pixel_stride;
1508 y = dest_ptr[y_pos] + overlay->shading_value;
1510 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1515 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1516 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1517 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1519 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1520 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1525 dest_ptr = dest->data[0];
1527 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1528 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1530 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1531 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1533 for (i = y0; i < y1; i++) {
1534 for (j = x0; j < x1; j++) {
1537 y_pos = (i * 4 * overlay->width) + j * 4;
1538 for (k = 0; k < 4; k++) {
1539 y = dest_ptr[y_pos + k] + overlay->shading_value;
1540 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1546 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1547 static inline void \
1548 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1549 gint x0, gint x1, gint y0, gint y1) \
1554 dest_ptr = dest->data[0];\
1556 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1557 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1559 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1560 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1562 for (i = y0; i < y1; i++) {\
1563 for (j = x0; j < x1; j++) {\
1565 y_pos = (i * 4 * overlay->width) + j * 4;\
1566 for (k = OFFSET; k < 3+OFFSET; k++) {\
1567 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1568 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1573 ARGB_SHADE_FUNCTION (ARGB, 1);
1574 ARGB_SHADE_FUNCTION (ABGR, 1);
1575 ARGB_SHADE_FUNCTION (RGBA, 0);
1576 ARGB_SHADE_FUNCTION (BGRA, 0);
1579 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1580 const gchar * text, gint textlen)
1584 if (!overlay->need_render) {
1585 GST_DEBUG ("Using previously rendered text.");
1589 /* -1 is the whole string */
1590 if (text != NULL && textlen < 0) {
1591 textlen = strlen (text);
1595 string = g_strndup (text, textlen);
1596 } else { /* empty string */
1597 string = g_strdup (" ");
1599 g_strdelimit (string, "\r\t", ' ');
1600 textlen = strlen (string);
1602 /* FIXME: should we check for UTF-8 here? */
1604 GST_DEBUG ("Rendering '%s'", string);
1605 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1609 overlay->need_render = FALSE;
1612 static GstFlowReturn
1613 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1614 GstBuffer * video_frame)
1617 GstVideoFrame frame;
1619 if (overlay->composition == NULL)
1622 if (gst_pad_check_reconfigure (overlay->srcpad))
1623 gst_base_text_overlay_negotiate (overlay);
1625 video_frame = gst_buffer_make_writable (video_frame);
1627 if (overlay->attach_compo_to_buffer) {
1628 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1629 gst_buffer_add_video_overlay_composition_meta (video_frame,
1630 overlay->composition);
1631 /* FIXME: emulate shaded background box if want_shading=true */
1635 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1639 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1641 /* shaded background box */
1642 if (overlay->want_shading) {
1643 switch (overlay->format) {
1644 case GST_VIDEO_FORMAT_I420:
1645 case GST_VIDEO_FORMAT_NV12:
1646 case GST_VIDEO_FORMAT_NV21:
1647 gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1648 xpos, xpos + overlay->image_width,
1649 ypos, ypos + overlay->image_height);
1651 case GST_VIDEO_FORMAT_AYUV:
1652 case GST_VIDEO_FORMAT_UYVY:
1653 gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1654 xpos, xpos + overlay->image_width,
1655 ypos, ypos + overlay->image_height);
1657 case GST_VIDEO_FORMAT_xRGB:
1658 gst_base_text_overlay_shade_xRGB (overlay, &frame,
1659 xpos, xpos + overlay->image_width,
1660 ypos, ypos + overlay->image_height);
1662 case GST_VIDEO_FORMAT_xBGR:
1663 gst_base_text_overlay_shade_xBGR (overlay, &frame,
1664 xpos, xpos + overlay->image_width,
1665 ypos, ypos + overlay->image_height);
1667 case GST_VIDEO_FORMAT_BGRx:
1668 gst_base_text_overlay_shade_BGRx (overlay, &frame,
1669 xpos, xpos + overlay->image_width,
1670 ypos, ypos + overlay->image_height);
1672 case GST_VIDEO_FORMAT_RGBx:
1673 gst_base_text_overlay_shade_RGBx (overlay, &frame,
1674 xpos, xpos + overlay->image_width,
1675 ypos, ypos + overlay->image_height);
1677 case GST_VIDEO_FORMAT_ARGB:
1678 gst_base_text_overlay_shade_ARGB (overlay, &frame,
1679 xpos, xpos + overlay->image_width,
1680 ypos, ypos + overlay->image_height);
1682 case GST_VIDEO_FORMAT_ABGR:
1683 gst_base_text_overlay_shade_ABGR (overlay, &frame,
1684 xpos, xpos + overlay->image_width,
1685 ypos, ypos + overlay->image_height);
1687 case GST_VIDEO_FORMAT_RGBA:
1688 gst_base_text_overlay_shade_RGBA (overlay, &frame,
1689 xpos, xpos + overlay->image_width,
1690 ypos, ypos + overlay->image_height);
1692 case GST_VIDEO_FORMAT_BGRA:
1693 gst_base_text_overlay_shade_BGRA (overlay, &frame,
1694 xpos, xpos + overlay->image_width,
1695 ypos, ypos + overlay->image_height);
1698 g_assert_not_reached ();
1702 gst_video_overlay_composition_blend (overlay->composition, &frame);
1704 gst_video_frame_unmap (&frame);
1708 return gst_pad_push (overlay->srcpad, video_frame);
1713 gst_buffer_unref (video_frame);
1714 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1719 static GstPadLinkReturn
1720 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1722 GstBaseTextOverlay *overlay;
1724 overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1725 if (G_UNLIKELY (!overlay))
1726 return GST_PAD_LINK_REFUSED;
1728 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1730 overlay->text_linked = TRUE;
1732 gst_object_unref (overlay);
1734 return GST_PAD_LINK_OK;
1738 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
1740 GstBaseTextOverlay *overlay;
1742 /* don't use gst_pad_get_parent() here, will deadlock */
1743 overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1745 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1747 overlay->text_linked = FALSE;
1749 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1753 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1756 gboolean ret = FALSE;
1757 GstBaseTextOverlay *overlay = NULL;
1759 overlay = GST_BASE_TEXT_OVERLAY (parent);
1761 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1763 switch (GST_EVENT_TYPE (event)) {
1764 case GST_EVENT_CAPS:
1768 gst_event_parse_caps (event, &caps);
1769 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1770 gst_event_unref (event);
1773 case GST_EVENT_SEGMENT:
1775 const GstSegment *segment;
1777 overlay->text_eos = FALSE;
1779 gst_event_parse_segment (event, &segment);
1781 if (segment->format == GST_FORMAT_TIME) {
1782 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1783 gst_segment_copy_into (segment, &overlay->text_segment);
1784 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1785 &overlay->text_segment);
1786 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1788 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1789 ("received non-TIME newsegment event on text input"));
1792 gst_event_unref (event);
1795 /* wake up the video chain, it might be waiting for a text buffer or
1796 * a text segment update */
1797 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1798 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1799 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1802 case GST_EVENT_FLUSH_STOP:
1803 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1804 GST_INFO_OBJECT (overlay, "text flush stop");
1805 overlay->text_flushing = FALSE;
1806 overlay->text_eos = FALSE;
1807 gst_base_text_overlay_pop_text (overlay);
1808 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1809 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1810 gst_event_unref (event);
1813 case GST_EVENT_FLUSH_START:
1814 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1815 GST_INFO_OBJECT (overlay, "text flush start");
1816 overlay->text_flushing = TRUE;
1817 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1818 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1819 gst_event_unref (event);
1823 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1824 overlay->text_eos = TRUE;
1825 GST_INFO_OBJECT (overlay, "text EOS");
1826 /* wake up the video chain, it might be waiting for a text buffer or
1827 * a text segment update */
1828 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1829 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1830 gst_event_unref (event);
1834 ret = gst_pad_event_default (pad, parent, event);
1842 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1845 gboolean ret = FALSE;
1846 GstBaseTextOverlay *overlay = NULL;
1848 overlay = GST_BASE_TEXT_OVERLAY (parent);
1850 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1852 switch (GST_EVENT_TYPE (event)) {
1853 case GST_EVENT_CAPS:
1857 gst_event_parse_caps (event, &caps);
1858 ret = gst_base_text_overlay_setcaps (overlay, caps);
1859 gst_event_unref (event);
1862 case GST_EVENT_SEGMENT:
1864 const GstSegment *segment;
1866 GST_DEBUG_OBJECT (overlay, "received new segment");
1868 gst_event_parse_segment (event, &segment);
1870 if (segment->format == GST_FORMAT_TIME) {
1871 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1874 gst_segment_copy_into (segment, &overlay->segment);
1876 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1877 ("received non-TIME newsegment event on video input"));
1880 ret = gst_pad_event_default (pad, parent, event);
1884 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1885 GST_INFO_OBJECT (overlay, "video EOS");
1886 overlay->video_eos = TRUE;
1887 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1888 ret = gst_pad_event_default (pad, parent, event);
1890 case GST_EVENT_FLUSH_START:
1891 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1892 GST_INFO_OBJECT (overlay, "video flush start");
1893 overlay->video_flushing = TRUE;
1894 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1895 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1896 ret = gst_pad_event_default (pad, parent, event);
1898 case GST_EVENT_FLUSH_STOP:
1899 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1900 GST_INFO_OBJECT (overlay, "video flush stop");
1901 overlay->video_flushing = FALSE;
1902 overlay->video_eos = FALSE;
1903 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1904 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1905 ret = gst_pad_event_default (pad, parent, event);
1908 ret = gst_pad_event_default (pad, parent, event);
1916 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1919 gboolean ret = FALSE;
1920 GstBaseTextOverlay *overlay;
1922 overlay = GST_BASE_TEXT_OVERLAY (parent);
1924 switch (GST_QUERY_TYPE (query)) {
1925 case GST_QUERY_CAPS:
1927 GstCaps *filter, *caps;
1929 gst_query_parse_caps (query, &filter);
1930 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1931 gst_query_set_caps_result (query, caps);
1932 gst_caps_unref (caps);
1937 ret = gst_pad_query_default (pad, parent, query);
1944 /* Called with lock held */
1946 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1948 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1950 if (overlay->text_buffer) {
1951 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1952 overlay->text_buffer);
1953 gst_buffer_unref (overlay->text_buffer);
1954 overlay->text_buffer = NULL;
1957 /* Let the text task know we used that buffer */
1958 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1961 /* We receive text buffers here. If they are out of segment we just ignore them.
1962 If the buffer is in our segment we keep it internally except if another one
1963 is already waiting here, in that case we wait that it gets kicked out */
1964 static GstFlowReturn
1965 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1968 GstFlowReturn ret = GST_FLOW_OK;
1969 GstBaseTextOverlay *overlay = NULL;
1970 gboolean in_seg = FALSE;
1971 guint64 clip_start = 0, clip_stop = 0;
1973 overlay = GST_BASE_TEXT_OVERLAY (parent);
1975 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1977 if (overlay->text_flushing) {
1978 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1979 ret = GST_FLOW_FLUSHING;
1980 GST_LOG_OBJECT (overlay, "text flushing");
1984 if (overlay->text_eos) {
1985 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1987 GST_LOG_OBJECT (overlay, "text EOS");
1991 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1992 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1993 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1994 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1995 GST_BUFFER_DURATION (buffer)));
1997 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2000 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2001 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2003 stop = GST_CLOCK_TIME_NONE;
2005 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2006 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2012 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2013 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2014 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2015 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2017 /* Wait for the previous buffer to go away */
2018 while (overlay->text_buffer != NULL) {
2019 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2020 GST_DEBUG_PAD_NAME (pad));
2021 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2022 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2023 if (overlay->text_flushing) {
2024 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2025 ret = GST_FLOW_FLUSHING;
2030 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2031 overlay->text_segment.position = clip_start;
2033 overlay->text_buffer = buffer;
2034 /* That's a new text buffer we need to render */
2035 overlay->need_render = TRUE;
2037 /* in case the video chain is waiting for a text buffer, wake it up */
2038 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2041 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2048 static GstFlowReturn
2049 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2052 GstBaseTextOverlayClass *klass;
2053 GstBaseTextOverlay *overlay;
2054 GstFlowReturn ret = GST_FLOW_OK;
2055 gboolean in_seg = FALSE;
2056 guint64 start, stop, clip_start = 0, clip_stop = 0;
2059 overlay = GST_BASE_TEXT_OVERLAY (parent);
2060 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2062 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2063 goto missing_timestamp;
2065 /* ignore buffers that are outside of the current segment */
2066 start = GST_BUFFER_TIMESTAMP (buffer);
2068 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2069 stop = GST_CLOCK_TIME_NONE;
2071 stop = start + GST_BUFFER_DURATION (buffer);
2074 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2075 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2076 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2078 /* segment_clip() will adjust start unconditionally to segment_start if
2079 * no stop time is provided, so handle this ourselves */
2080 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2081 goto out_of_segment;
2083 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2084 &clip_start, &clip_stop);
2087 goto out_of_segment;
2089 /* if the buffer is only partially in the segment, fix up stamps */
2090 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2091 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2092 buffer = gst_buffer_make_writable (buffer);
2093 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2095 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2098 /* now, after we've done the clipping, fix up end time if there's no
2099 * duration (we only use those estimated values internally though, we
2100 * don't want to set bogus values on the buffer itself) */
2104 gint fps_num, fps_denom;
2106 /* FIXME, store this in setcaps */
2107 caps = gst_pad_get_current_caps (pad);
2108 s = gst_caps_get_structure (caps, 0);
2109 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2110 fps_num && fps_denom) {
2111 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2112 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2114 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2115 stop = start + 1; /* we need to assume some interval */
2117 gst_caps_unref (caps);
2120 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2124 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2126 if (overlay->video_flushing)
2129 if (overlay->video_eos)
2132 if (overlay->silent) {
2133 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2134 ret = gst_pad_push (overlay->srcpad, buffer);
2136 /* Update position */
2137 overlay->segment.position = clip_start;
2142 /* Text pad not linked, rendering internal text */
2143 if (!overlay->text_linked) {
2144 if (klass->get_text) {
2145 text = klass->get_text (overlay, buffer);
2147 text = g_strdup (overlay->default_text);
2150 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2151 "text: '%s'", GST_STR_NULL (text));
2153 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2155 if (text != NULL && *text != '\0') {
2156 /* Render and push */
2157 gst_base_text_overlay_render_text (overlay, text, -1);
2158 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2160 /* Invalid or empty string */
2161 ret = gst_pad_push (overlay->srcpad, buffer);
2164 /* Text pad linked, check if we have a text buffer queued */
2165 if (overlay->text_buffer) {
2166 gboolean pop_text = FALSE, valid_text_time = TRUE;
2167 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2168 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2169 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2170 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2171 GstClockTime vid_running_time, vid_running_time_end;
2173 /* if the text buffer isn't stamped right, pop it off the
2174 * queue and display it for the current video frame only */
2175 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2176 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2177 GST_WARNING_OBJECT (overlay,
2178 "Got text buffer with invalid timestamp or duration");
2180 valid_text_time = FALSE;
2182 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2183 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2187 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2189 vid_running_time_end =
2190 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2193 /* If timestamp and duration are valid */
2194 if (valid_text_time) {
2196 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2198 text_running_time_end =
2199 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2203 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2204 GST_TIME_ARGS (text_running_time),
2205 GST_TIME_ARGS (text_running_time_end));
2206 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2207 GST_TIME_ARGS (vid_running_time),
2208 GST_TIME_ARGS (vid_running_time_end));
2210 /* Text too old or in the future */
2211 if (valid_text_time && text_running_time_end <= vid_running_time) {
2212 /* text buffer too old, get rid of it and do nothing */
2213 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2215 gst_base_text_overlay_pop_text (overlay);
2216 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2217 goto wait_for_text_buf;
2218 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2219 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2220 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2221 /* Push the video frame */
2222 ret = gst_pad_push (overlay->srcpad, buffer);
2228 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2229 in_text = (gchar *) map.data;
2232 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2233 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2234 * here on purpose, this is something that needs fixing upstream */
2235 if (!g_utf8_validate (in_text, in_size, NULL)) {
2236 const gchar *end = NULL;
2238 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2239 in_text = g_strndup (in_text, in_size);
2240 while (!g_utf8_validate (in_text, in_size, &end) && end)
2241 *((gchar *) end) = '*';
2244 /* Get the string */
2245 if (overlay->have_pango_markup) {
2246 text = g_strndup (in_text, in_size);
2248 text = g_markup_escape_text (in_text, in_size);
2251 if (text != NULL && *text != '\0') {
2252 gint text_len = strlen (text);
2254 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2255 text[text_len - 1] == '\r')) {
2258 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2259 gst_base_text_overlay_render_text (overlay, text, text_len);
2261 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2262 gst_base_text_overlay_render_text (overlay, " ", 1);
2264 if (in_text != (gchar *) map.data)
2267 gst_buffer_unmap (overlay->text_buffer, &map);
2269 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2270 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2272 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2273 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2278 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2279 gst_base_text_overlay_pop_text (overlay);
2280 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2283 gboolean wait_for_text_buf = TRUE;
2285 if (overlay->text_eos)
2286 wait_for_text_buf = FALSE;
2288 if (!overlay->wait_text)
2289 wait_for_text_buf = FALSE;
2291 /* Text pad linked, but no text buffer available - what now? */
2292 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2293 GstClockTime text_start_running_time, text_position_running_time;
2294 GstClockTime vid_running_time;
2297 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2298 GST_BUFFER_TIMESTAMP (buffer));
2299 text_start_running_time =
2300 gst_segment_to_running_time (&overlay->text_segment,
2301 GST_FORMAT_TIME, overlay->text_segment.start);
2302 text_position_running_time =
2303 gst_segment_to_running_time (&overlay->text_segment,
2304 GST_FORMAT_TIME, overlay->text_segment.position);
2306 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2307 vid_running_time < text_start_running_time) ||
2308 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2309 vid_running_time < text_position_running_time)) {
2310 wait_for_text_buf = FALSE;
2314 if (wait_for_text_buf) {
2315 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2316 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2317 GST_DEBUG_OBJECT (overlay, "resuming");
2318 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2319 goto wait_for_text_buf;
2321 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2322 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2323 ret = gst_pad_push (overlay->srcpad, buffer);
2330 /* Update position */
2331 overlay->segment.position = clip_start;
2337 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2338 gst_buffer_unref (buffer);
2344 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2345 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2346 gst_buffer_unref (buffer);
2347 return GST_FLOW_FLUSHING;
2351 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2352 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2353 gst_buffer_unref (buffer);
2354 return GST_FLOW_EOS;
2358 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2359 gst_buffer_unref (buffer);
2364 static GstStateChangeReturn
2365 gst_base_text_overlay_change_state (GstElement * element,
2366 GstStateChange transition)
2368 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2369 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2371 switch (transition) {
2372 case GST_STATE_CHANGE_PAUSED_TO_READY:
2373 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2374 overlay->text_flushing = TRUE;
2375 overlay->video_flushing = TRUE;
2376 /* pop_text will broadcast on the GCond and thus also make the video
2377 * chain exit if it's waiting for a text buffer */
2378 gst_base_text_overlay_pop_text (overlay);
2379 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2385 ret = parent_class->change_state (element, transition);
2386 if (ret == GST_STATE_CHANGE_FAILURE)
2389 switch (transition) {
2390 case GST_STATE_CHANGE_READY_TO_PAUSED:
2391 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2392 overlay->text_flushing = FALSE;
2393 overlay->video_flushing = FALSE;
2394 overlay->video_eos = FALSE;
2395 overlay->text_eos = FALSE;
2396 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2397 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2398 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2408 plugin_init (GstPlugin * plugin)
2410 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2411 GST_TYPE_TEXT_OVERLAY) ||
2412 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2413 GST_TYPE_TIME_OVERLAY) ||
2414 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2415 GST_TYPE_CLOCK_OVERLAY) ||
2416 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2417 GST_TYPE_TEXT_RENDER)) {
2421 /*texttestsrc_plugin_init(module, plugin); */
2423 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2428 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2429 pango, "Pango-based text rendering and overlay", plugin_init,
2430 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)