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_XPAD 25
102 #define DEFAULT_PROP_YPAD 25
103 #define DEFAULT_PROP_DELTAX 0
104 #define DEFAULT_PROP_DELTAY 0
105 #define DEFAULT_PROP_XPOS 0.5
106 #define DEFAULT_PROP_YPOS 0.5
107 #define DEFAULT_PROP_WRAP_MODE GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
108 #define DEFAULT_PROP_FONT_DESC ""
109 #define DEFAULT_PROP_SILENT FALSE
110 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
111 #define DEFAULT_PROP_WAIT_TEXT TRUE
112 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
113 #define DEFAULT_PROP_VERTICAL_RENDER FALSE
114 #define DEFAULT_PROP_COLOR 0xffffffff
115 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
117 /* make a property of me */
118 #define DEFAULT_SHADING_VALUE -80
120 #define MINIMUM_OUTLINE_OFFSET 1.0
121 #define DEFAULT_SCALE_BASIS 640
123 #define COMP_Y(ret, r, g, b) \
125 ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
126 ret = CLAMP (ret, 0, 255); \
129 #define COMP_U(ret, r, g, b) \
131 ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
132 ret = CLAMP (ret, 0, 255); \
135 #define COMP_V(ret, r, g, b) \
137 ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
138 ret = CLAMP (ret, 0, 255); \
141 #define BLEND(ret, alpha, v0, v1) \
143 ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
146 #define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew) \
149 _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \
150 ret = CLAMP (_tmp, 0, 255); \
153 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
154 # define CAIRO_ARGB_A 3
155 # define CAIRO_ARGB_R 2
156 # define CAIRO_ARGB_G 1
157 # define CAIRO_ARGB_B 0
159 # define CAIRO_ARGB_A 0
160 # define CAIRO_ARGB_R 1
161 # define CAIRO_ARGB_G 2
162 # define CAIRO_ARGB_B 3
183 PROP_AUTO_ADJUST_SIZE,
184 PROP_VERTICAL_RENDER,
191 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
192 I420, YV12, AYUV, YUY2, UYVY, v308, v210, v216, Y41B, Y42B, Y444, \
193 Y800, Y16, NV12, NV21, UYVP, A420, YUV9, IYU1 }"
195 static GstStaticPadTemplate src_template_factory =
196 GST_STATIC_PAD_TEMPLATE ("src",
199 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
202 static GstStaticPadTemplate video_sink_template_factory =
203 GST_STATIC_PAD_TEMPLATE ("video_sink",
206 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
209 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
211 gst_base_text_overlay_valign_get_type (void)
213 static GType base_text_overlay_valign_type = 0;
214 static const GEnumValue base_text_overlay_valign[] = {
215 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
216 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
217 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
218 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
219 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
223 if (!base_text_overlay_valign_type) {
224 base_text_overlay_valign_type =
225 g_enum_register_static ("GstBaseTextOverlayVAlign",
226 base_text_overlay_valign);
228 return base_text_overlay_valign_type;
231 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
233 gst_base_text_overlay_halign_get_type (void)
235 static GType base_text_overlay_halign_type = 0;
236 static const GEnumValue base_text_overlay_halign[] = {
237 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
238 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
239 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
240 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
244 if (!base_text_overlay_halign_type) {
245 base_text_overlay_halign_type =
246 g_enum_register_static ("GstBaseTextOverlayHAlign",
247 base_text_overlay_halign);
249 return base_text_overlay_halign_type;
253 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
255 gst_base_text_overlay_wrap_mode_get_type (void)
257 static GType base_text_overlay_wrap_mode_type = 0;
258 static const GEnumValue base_text_overlay_wrap_mode[] = {
259 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
260 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
261 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
262 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
266 if (!base_text_overlay_wrap_mode_type) {
267 base_text_overlay_wrap_mode_type =
268 g_enum_register_static ("GstBaseTextOverlayWrapMode",
269 base_text_overlay_wrap_mode);
271 return base_text_overlay_wrap_mode_type;
274 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
276 gst_base_text_overlay_line_align_get_type (void)
278 static GType base_text_overlay_line_align_type = 0;
279 static const GEnumValue base_text_overlay_line_align[] = {
280 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
281 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
282 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
286 if (!base_text_overlay_line_align_type) {
287 base_text_overlay_line_align_type =
288 g_enum_register_static ("GstBaseTextOverlayLineAlign",
289 base_text_overlay_line_align);
291 return base_text_overlay_line_align_type;
294 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
295 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
296 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
297 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
298 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
299 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
300 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
302 static GstElementClass *parent_class = NULL;
303 static void gst_base_text_overlay_base_init (gpointer g_class);
304 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
305 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
306 GstBaseTextOverlayClass * klass);
308 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
309 element, GstStateChange transition);
311 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
312 GstBaseTextOverlay * overlay, GstCaps * filter);
313 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
315 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
317 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
318 GstObject * parent, GstEvent * event);
319 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
320 GstObject * parent, GstQuery * query);
322 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
323 GstObject * parent, GstEvent * event);
324 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
325 GstObject * parent, GstQuery * query);
326 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
327 GstObject * parent, GstBuffer * buffer);
329 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
330 GstObject * parent, GstEvent * event);
331 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
332 GstObject * parent, GstBuffer * buffer);
333 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
335 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad);
336 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
337 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
340 static void gst_base_text_overlay_finalize (GObject * object);
341 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
342 const GValue * value, GParamSpec * pspec);
343 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
344 GValue * value, GParamSpec * pspec);
346 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
347 PangoFontDescription * desc);
350 gst_base_text_overlay_get_type (void)
352 static GType type = 0;
354 if (g_once_init_enter ((gsize *) & type)) {
355 static const GTypeInfo info = {
356 sizeof (GstBaseTextOverlayClass),
357 (GBaseInitFunc) gst_base_text_overlay_base_init,
359 (GClassInitFunc) gst_base_text_overlay_class_init,
362 sizeof (GstBaseTextOverlay),
364 (GInstanceInitFunc) gst_base_text_overlay_init,
367 g_once_init_leave ((gsize *) & type,
368 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
376 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
377 GstBuffer * video_frame)
379 return g_strdup (overlay->default_text);
383 gst_base_text_overlay_base_init (gpointer g_class)
385 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
386 PangoFontMap *fontmap;
388 /* Only lock for the subclasses here, the base class
389 * doesn't have this mutex yet and it's not necessary
391 if (klass->pango_lock)
392 g_mutex_lock (klass->pango_lock);
393 fontmap = pango_cairo_font_map_get_default ();
394 klass->pango_context =
395 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
396 if (klass->pango_lock)
397 g_mutex_unlock (klass->pango_lock);
401 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
403 GObjectClass *gobject_class;
404 GstElementClass *gstelement_class;
406 gobject_class = (GObjectClass *) klass;
407 gstelement_class = (GstElementClass *) klass;
409 parent_class = g_type_class_peek_parent (klass);
411 gobject_class->finalize = gst_base_text_overlay_finalize;
412 gobject_class->set_property = gst_base_text_overlay_set_property;
413 gobject_class->get_property = gst_base_text_overlay_get_property;
415 gst_element_class_add_pad_template (gstelement_class,
416 gst_static_pad_template_get (&src_template_factory));
417 gst_element_class_add_pad_template (gstelement_class,
418 gst_static_pad_template_get (&video_sink_template_factory));
420 gstelement_class->change_state =
421 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
423 klass->pango_lock = g_slice_new (GMutex);
424 g_mutex_init (klass->pango_lock);
426 klass->get_text = gst_base_text_overlay_get_text;
428 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
429 g_param_spec_string ("text", "text",
430 "Text to be display.", DEFAULT_PROP_TEXT,
431 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
432 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
433 g_param_spec_boolean ("shaded-background", "shaded background",
434 "Whether to shade the background under the text area",
435 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
436 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
437 g_param_spec_enum ("valignment", "vertical alignment",
438 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
439 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
440 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
441 g_param_spec_enum ("halignment", "horizontal alignment",
442 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
443 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
444 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
445 g_param_spec_int ("xpad", "horizontal paddding",
446 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
447 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
448 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
449 g_param_spec_int ("ypad", "vertical padding",
450 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
451 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
452 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
453 g_param_spec_int ("deltax", "X position modifier",
454 "Shift X position to the left or to the right. Unit is pixels.",
455 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
456 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
457 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
458 g_param_spec_int ("deltay", "Y position modifier",
459 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
460 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
462 * GstBaseTextOverlay:xpos
464 * Horizontal position of the rendered text when using positioned alignment.
468 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
469 g_param_spec_double ("xpos", "horizontal position",
470 "Horizontal position when using position alignment", 0, 1.0,
472 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
474 * GstBaseTextOverlay:ypos
476 * Vertical position of the rendered text when using positioned alignment.
480 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
481 g_param_spec_double ("ypos", "vertical position",
482 "Vertical position when using position alignment", 0, 1.0,
484 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
485 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
486 g_param_spec_enum ("wrap-mode", "wrap mode",
487 "Whether to wrap the text and if so how.",
488 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
489 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
490 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
491 g_param_spec_string ("font-desc", "font description",
492 "Pango font description of font to be used for rendering. "
493 "See documentation of pango_font_description_from_string "
494 "for syntax.", DEFAULT_PROP_FONT_DESC,
495 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
497 * GstBaseTextOverlay:color
499 * Color of the rendered text.
503 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
504 g_param_spec_uint ("color", "Color",
505 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
507 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
509 * GstTextOverlay:outline-color
511 * Color of the outline of the rendered text.
515 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
516 g_param_spec_uint ("outline-color", "Text Outline Color",
517 "Color to use for outline the text (big-endian ARGB).", 0,
518 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
519 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
522 * GstBaseTextOverlay:line-alignment
524 * Alignment of text lines relative to each other (for multi-line text)
528 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
529 g_param_spec_enum ("line-alignment", "line alignment",
530 "Alignment of text lines relative to each other.",
531 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
532 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
534 * GstBaseTextOverlay:silent
536 * If set, no text is rendered. Useful to switch off text rendering
537 * temporarily without removing the textoverlay element from the pipeline.
541 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
542 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
543 g_param_spec_boolean ("silent", "silent",
544 "Whether to render the text string",
546 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
548 * GstBaseTextOverlay:wait-text
550 * If set, the video will block until a subtitle is received on the text pad.
551 * If video and subtitles are sent in sync, like from the same demuxer, this
552 * property should be set.
556 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
557 g_param_spec_boolean ("wait-text", "Wait Text",
558 "Whether to wait for subtitles",
559 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
561 g_object_class_install_property (G_OBJECT_CLASS (klass),
562 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
563 "Automatically adjust font size to screen-size.",
564 DEFAULT_PROP_AUTO_ADJUST_SIZE,
565 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
567 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
568 g_param_spec_boolean ("vertical-render", "vertical render",
569 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
570 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
574 gst_base_text_overlay_finalize (GObject * object)
576 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
578 g_free (overlay->default_text);
580 if (overlay->composition) {
581 gst_video_overlay_composition_unref (overlay->composition);
582 overlay->composition = NULL;
585 if (overlay->text_image) {
586 gst_buffer_unref (overlay->text_image);
587 overlay->text_image = NULL;
590 if (overlay->layout) {
591 g_object_unref (overlay->layout);
592 overlay->layout = NULL;
595 if (overlay->text_buffer) {
596 gst_buffer_unref (overlay->text_buffer);
597 overlay->text_buffer = NULL;
600 g_mutex_clear (&overlay->lock);
601 g_cond_clear (&overlay->cond);
603 G_OBJECT_CLASS (parent_class)->finalize (object);
607 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
608 GstBaseTextOverlayClass * klass)
610 GstPadTemplate *template;
611 PangoFontDescription *desc;
614 template = gst_static_pad_template_get (&video_sink_template_factory);
615 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
616 gst_object_unref (template);
617 gst_pad_set_event_function (overlay->video_sinkpad,
618 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
619 gst_pad_set_chain_function (overlay->video_sinkpad,
620 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
621 gst_pad_set_query_function (overlay->video_sinkpad,
622 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
623 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
626 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
630 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
631 gst_object_unref (template);
633 gst_pad_set_event_function (overlay->text_sinkpad,
634 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
635 gst_pad_set_chain_function (overlay->text_sinkpad,
636 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
637 gst_pad_set_link_function (overlay->text_sinkpad,
638 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
639 gst_pad_set_unlink_function (overlay->text_sinkpad,
640 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
641 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
645 template = gst_static_pad_template_get (&src_template_factory);
646 overlay->srcpad = gst_pad_new_from_template (template, "src");
647 gst_object_unref (template);
648 gst_pad_set_event_function (overlay->srcpad,
649 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
650 gst_pad_set_query_function (overlay->srcpad,
651 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
652 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
654 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
655 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
657 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
658 (overlay)->pango_context);
660 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
661 (overlay)->pango_context);
662 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
664 overlay->color = DEFAULT_PROP_COLOR;
665 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
666 overlay->halign = DEFAULT_PROP_HALIGNMENT;
667 overlay->valign = DEFAULT_PROP_VALIGNMENT;
668 overlay->xpad = DEFAULT_PROP_XPAD;
669 overlay->ypad = DEFAULT_PROP_YPAD;
670 overlay->deltax = DEFAULT_PROP_DELTAX;
671 overlay->deltay = DEFAULT_PROP_DELTAY;
672 overlay->xpos = DEFAULT_PROP_XPOS;
673 overlay->ypos = DEFAULT_PROP_YPOS;
675 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
677 overlay->want_shading = DEFAULT_PROP_SHADING;
678 overlay->shading_value = DEFAULT_SHADING_VALUE;
679 overlay->silent = DEFAULT_PROP_SILENT;
680 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
681 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
683 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
684 overlay->need_render = TRUE;
685 overlay->text_image = NULL;
686 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
687 gst_base_text_overlay_update_render_mode (overlay);
689 overlay->text_buffer = NULL;
690 overlay->text_linked = FALSE;
691 g_mutex_init (&overlay->lock);
692 g_cond_init (&overlay->cond);
693 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
694 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
698 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
700 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
701 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
702 pango_layout_set_width (overlay->layout, -1);
706 if (overlay->auto_adjust_size) {
707 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
708 if (overlay->use_vertical_render) {
709 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
713 (overlay->use_vertical_render ? overlay->height : overlay->width) *
717 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
718 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
719 pango_layout_set_width (overlay->layout, width);
720 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
725 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
727 PangoMatrix matrix = PANGO_MATRIX_INIT;
728 PangoContext *context = pango_layout_get_context (overlay->layout);
730 if (overlay->use_vertical_render) {
731 pango_matrix_rotate (&matrix, -90);
732 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
733 pango_context_set_matrix (context, &matrix);
734 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
736 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
737 pango_context_set_matrix (context, &matrix);
738 pango_layout_set_alignment (overlay->layout,
739 (PangoAlignment) overlay->line_align);
744 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
746 GstStructure *structure;
748 structure = gst_caps_get_structure (caps, 0);
749 overlay->have_pango_markup =
750 gst_structure_has_name (structure, "text/x-pango-markup");
755 /* FIXME: upstream nego (e.g. when the video window is resized) */
757 /* only negotiate/query video overlay composition support for now */
759 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
763 gboolean attach = FALSE;
765 GST_DEBUG_OBJECT (overlay, "performing negotiation");
767 target = gst_pad_get_current_caps (overlay->srcpad);
769 if (!target || gst_caps_is_empty (target))
772 /* find supported meta */
773 query = gst_query_new_allocation (target, TRUE);
775 if (!gst_pad_peer_query (overlay->srcpad, query)) {
776 /* no problem, we use the query defaults */
777 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
780 if (gst_query_find_allocation_meta (query,
781 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
784 overlay->attach_compo_to_buffer = attach;
786 gst_query_unref (query);
787 gst_caps_unref (target);
794 gst_caps_unref (target);
800 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
803 gboolean ret = FALSE;
805 if (!gst_video_info_from_caps (&info, caps))
808 overlay->info = info;
809 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
810 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
811 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
813 ret = gst_pad_set_caps (overlay->srcpad, caps);
816 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
817 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
818 gst_base_text_overlay_negotiate (overlay);
819 gst_base_text_overlay_update_wrap_mode (overlay);
820 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
821 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
829 GST_DEBUG_OBJECT (overlay, "could not parse caps");
835 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
836 const GValue * value, GParamSpec * pspec)
838 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
840 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
843 g_free (overlay->default_text);
844 overlay->default_text = g_value_dup_string (value);
845 overlay->need_render = TRUE;
848 overlay->want_shading = g_value_get_boolean (value);
851 overlay->xpad = g_value_get_int (value);
854 overlay->ypad = g_value_get_int (value);
857 overlay->deltax = g_value_get_int (value);
860 overlay->deltay = g_value_get_int (value);
863 overlay->xpos = g_value_get_double (value);
866 overlay->ypos = g_value_get_double (value);
868 case PROP_VALIGNMENT:
869 overlay->valign = g_value_get_enum (value);
871 case PROP_HALIGNMENT:
872 overlay->halign = g_value_get_enum (value);
875 overlay->wrap_mode = g_value_get_enum (value);
876 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
877 gst_base_text_overlay_update_wrap_mode (overlay);
878 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
882 PangoFontDescription *desc;
883 const gchar *fontdesc_str;
885 fontdesc_str = g_value_get_string (value);
886 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
887 desc = pango_font_description_from_string (fontdesc_str);
889 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
890 pango_layout_set_font_description (overlay->layout, desc);
891 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
892 pango_font_description_free (desc);
894 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
897 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
901 overlay->color = g_value_get_uint (value);
903 case PROP_OUTLINE_COLOR:
904 overlay->outline_color = g_value_get_uint (value);
907 overlay->silent = g_value_get_boolean (value);
909 case PROP_LINE_ALIGNMENT:
910 overlay->line_align = g_value_get_enum (value);
911 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
912 pango_layout_set_alignment (overlay->layout,
913 (PangoAlignment) overlay->line_align);
914 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
917 overlay->wait_text = g_value_get_boolean (value);
919 case PROP_AUTO_ADJUST_SIZE:
920 overlay->auto_adjust_size = g_value_get_boolean (value);
921 overlay->need_render = TRUE;
923 case PROP_VERTICAL_RENDER:
924 overlay->use_vertical_render = g_value_get_boolean (value);
925 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
926 gst_base_text_overlay_update_render_mode (overlay);
927 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
928 overlay->need_render = TRUE;
931 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
935 overlay->need_render = TRUE;
936 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
940 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
941 GValue * value, GParamSpec * pspec)
943 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
945 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
948 g_value_set_string (value, overlay->default_text);
951 g_value_set_boolean (value, overlay->want_shading);
954 g_value_set_int (value, overlay->xpad);
957 g_value_set_int (value, overlay->ypad);
960 g_value_set_int (value, overlay->deltax);
963 g_value_set_int (value, overlay->deltay);
966 g_value_set_double (value, overlay->xpos);
969 g_value_set_double (value, overlay->ypos);
971 case PROP_VALIGNMENT:
972 g_value_set_enum (value, overlay->valign);
974 case PROP_HALIGNMENT:
975 g_value_set_enum (value, overlay->halign);
978 g_value_set_enum (value, overlay->wrap_mode);
981 g_value_set_boolean (value, overlay->silent);
983 case PROP_LINE_ALIGNMENT:
984 g_value_set_enum (value, overlay->line_align);
987 g_value_set_boolean (value, overlay->wait_text);
989 case PROP_AUTO_ADJUST_SIZE:
990 g_value_set_boolean (value, overlay->auto_adjust_size);
992 case PROP_VERTICAL_RENDER:
993 g_value_set_boolean (value, overlay->use_vertical_render);
996 g_value_set_uint (value, overlay->color);
998 case PROP_OUTLINE_COLOR:
999 g_value_set_uint (value, overlay->outline_color);
1002 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1006 overlay->need_render = TRUE;
1007 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1011 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1014 gboolean ret = FALSE;
1015 GstBaseTextOverlay *overlay;
1017 overlay = GST_BASE_TEXT_OVERLAY (parent);
1019 switch (GST_QUERY_TYPE (query)) {
1020 case GST_QUERY_CAPS:
1022 GstCaps *filter, *caps;
1024 gst_query_parse_caps (query, &filter);
1025 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1026 gst_query_set_caps_result (query, caps);
1027 gst_caps_unref (caps);
1032 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1040 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1043 gboolean ret = FALSE;
1044 GstBaseTextOverlay *overlay = NULL;
1046 overlay = GST_BASE_TEXT_OVERLAY (parent);
1048 switch (GST_EVENT_TYPE (event)) {
1049 case GST_EVENT_SEEK:{
1052 /* We don't handle seek if we have not text pad */
1053 if (!overlay->text_linked) {
1054 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1055 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1059 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1061 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1063 /* Flush downstream, only for flushing seek */
1064 if (flags & GST_SEEK_FLAG_FLUSH)
1065 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1067 /* Mark ourself as flushing, unblock chains */
1068 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1069 overlay->video_flushing = TRUE;
1070 overlay->text_flushing = TRUE;
1071 gst_base_text_overlay_pop_text (overlay);
1072 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1074 /* Seek on each sink pad */
1075 gst_event_ref (event);
1076 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1078 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1080 gst_event_unref (event);
1085 if (overlay->text_linked) {
1086 gst_event_ref (event);
1087 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1088 gst_pad_push_event (overlay->text_sinkpad, event);
1090 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1101 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1107 if (G_UNLIKELY (!overlay))
1108 return gst_pad_get_pad_template_caps (pad);
1110 if (pad == overlay->srcpad)
1111 otherpad = overlay->video_sinkpad;
1113 otherpad = overlay->srcpad;
1115 /* we can do what the peer can */
1116 caps = gst_pad_peer_query_caps (otherpad, filter);
1118 GstCaps *temp, *templ;
1120 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1122 /* filtered against our padtemplate */
1123 templ = gst_pad_get_pad_template_caps (otherpad);
1124 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1125 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1126 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1127 gst_caps_unref (caps);
1128 gst_caps_unref (templ);
1129 /* this is what we can do */
1132 /* no peer, our padtemplate is enough then */
1133 caps = gst_pad_get_pad_template_caps (pad);
1135 GstCaps *intersection;
1138 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1139 gst_caps_unref (caps);
1140 caps = intersection;
1144 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1150 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1151 PangoFontDescription * desc)
1153 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1154 overlay->shadow_offset = (double) (font_size) / 13.0;
1155 overlay->outline_offset = (double) (font_size) / 15.0;
1156 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1157 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1161 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1162 gint * xpos, gint * ypos)
1165 GstBaseTextOverlayVAlign valign;
1166 GstBaseTextOverlayHAlign halign;
1168 width = overlay->image_width;
1169 height = overlay->image_height;
1171 if (overlay->use_vertical_render)
1172 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1174 halign = overlay->halign;
1177 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1178 *xpos = overlay->xpad;
1180 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1181 *xpos = (overlay->width - width) / 2;
1183 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1184 *xpos = overlay->width - width - overlay->xpad;
1186 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1187 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1188 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1195 *xpos += overlay->deltax;
1197 if (overlay->use_vertical_render)
1198 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1200 valign = overlay->valign;
1203 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1204 *ypos = overlay->height - height - overlay->ypad;
1206 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1207 *ypos = overlay->height - (height + overlay->ypad);
1209 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1210 *ypos = overlay->ypad;
1212 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1213 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1214 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1216 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1217 *ypos = (overlay->height - height) / 2;
1220 *ypos = overlay->ypad;
1223 *ypos += overlay->deltay;
1227 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1230 GstVideoOverlayRectangle *rectangle;
1232 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1234 if (overlay->text_image) {
1235 rectangle = gst_video_overlay_rectangle_new_argb (overlay->text_image,
1236 overlay->image_width, overlay->image_height, 4 * overlay->image_width,
1237 xpos, ypos, overlay->image_width, overlay->image_height,
1238 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1240 if (overlay->composition)
1241 gst_video_overlay_composition_unref (overlay->composition);
1242 overlay->composition = gst_video_overlay_composition_new (rectangle);
1243 gst_video_overlay_rectangle_unref (rectangle);
1245 } else if (overlay->composition) {
1246 gst_video_overlay_composition_unref (overlay->composition);
1247 overlay->composition = NULL;
1252 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1253 const gchar * string, gint textlen)
1256 cairo_surface_t *surface;
1257 PangoRectangle ink_rect, logical_rect;
1258 cairo_matrix_t cairo_matrix;
1260 double scalef = 1.0;
1265 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1267 if (overlay->auto_adjust_size) {
1268 /* 640 pixel is default */
1269 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1271 pango_layout_set_width (overlay->layout, -1);
1272 /* set text on pango layout */
1273 pango_layout_set_markup (overlay->layout, string, textlen);
1275 /* get subtitle image size */
1276 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1278 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1280 if (width + overlay->deltax >
1281 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1283 * subtitle image width is larger then overlay width
1284 * so rearrange overlay wrap mode.
1286 gst_base_text_overlay_update_wrap_mode (overlay);
1287 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1288 width = overlay->width;
1292 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1293 if (height > overlay->height) {
1294 height = overlay->height;
1296 if (overlay->use_vertical_render) {
1297 PangoRectangle rect;
1298 PangoContext *context;
1299 PangoMatrix matrix = PANGO_MATRIX_INIT;
1302 context = pango_layout_get_context (overlay->layout);
1304 pango_matrix_rotate (&matrix, -90);
1306 rect.x = rect.y = 0;
1308 rect.height = height;
1309 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1310 matrix.x0 = -rect.x;
1311 matrix.y0 = -rect.y;
1313 pango_context_set_matrix (context, &matrix);
1315 cairo_matrix.xx = matrix.xx;
1316 cairo_matrix.yx = matrix.yx;
1317 cairo_matrix.xy = matrix.xy;
1318 cairo_matrix.yy = matrix.yy;
1319 cairo_matrix.x0 = matrix.x0;
1320 cairo_matrix.y0 = matrix.y0;
1321 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1327 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1330 /* reallocate overlay buffer */
1331 buffer = gst_buffer_new_and_alloc (4 * width * height);
1332 gst_buffer_replace (&overlay->text_image, buffer);
1333 gst_buffer_unref (buffer);
1335 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1336 surface = cairo_image_surface_create_for_data (map.data,
1337 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1338 cr = cairo_create (surface);
1341 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1344 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1346 if (overlay->want_shading)
1347 cairo_paint_with_alpha (cr, overlay->shading_value);
1349 /* apply transformations */
1350 cairo_set_matrix (cr, &cairo_matrix);
1352 /* FIXME: We use show_layout everywhere except for the surface
1353 * because it's really faster and internally does all kinds of
1354 * caching. Unfortunately we have to paint to a cairo path for
1355 * the outline and this is slow. Once Pango supports user fonts
1356 * we should use them, see
1357 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1359 * Idea would the be, to create a cairo user font that
1360 * does shadow, outline, text painting in the
1361 * render_glyph function.
1364 /* draw shadow text */
1366 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1367 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1368 pango_cairo_show_layout (cr, overlay->layout);
1371 a = (overlay->outline_color >> 24) & 0xff;
1372 r = (overlay->outline_color >> 16) & 0xff;
1373 g = (overlay->outline_color >> 8) & 0xff;
1374 b = (overlay->outline_color >> 0) & 0xff;
1376 /* draw outline text */
1378 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1379 cairo_set_line_width (cr, overlay->outline_offset);
1380 pango_cairo_layout_path (cr, overlay->layout);
1384 a = (overlay->color >> 24) & 0xff;
1385 r = (overlay->color >> 16) & 0xff;
1386 g = (overlay->color >> 8) & 0xff;
1387 b = (overlay->color >> 0) & 0xff;
1391 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1392 pango_cairo_show_layout (cr, overlay->layout);
1396 cairo_surface_destroy (surface);
1397 gst_buffer_unmap (buffer, &map);
1398 overlay->image_width = width;
1399 overlay->image_height = height;
1400 overlay->baseline_y = ink_rect.y;
1401 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1403 gst_base_text_overlay_set_composition (overlay);
1410 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1411 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1413 gint i, j, dest_stride;
1416 dest_stride = dest->info.stride[0];
1417 dest_ptr = dest->data[0];
1419 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1420 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1422 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1423 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1425 for (i = y0; i < y1; ++i) {
1426 for (j = x0; j < x1; ++j) {
1427 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1429 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1435 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1436 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1439 guint dest_stride, pixel_stride;
1442 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1443 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1444 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1446 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1447 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1449 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1450 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1453 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1455 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1458 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1460 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1462 for (i = y0; i < y1; i++) {
1463 for (j = x0; j < x1; j++) {
1467 y_pos = (i * dest_stride) + j * pixel_stride;
1468 y = dest_ptr[y_pos] + overlay->shading_value;
1470 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1475 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1476 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1477 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1479 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1480 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1485 dest_ptr = dest->data[0];
1487 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1488 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1490 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1491 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1493 for (i = y0; i < y1; i++) {
1494 for (j = x0; j < x1; j++) {
1497 y_pos = (i * 4 * overlay->width) + j * 4;
1498 for (k = 0; k < 4; k++) {
1499 y = dest_ptr[y_pos + k] + overlay->shading_value;
1500 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1506 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1507 static inline void \
1508 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1509 gint x0, gint x1, gint y0, gint y1) \
1514 dest_ptr = dest->data[0];\
1516 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1517 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1519 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1520 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1522 for (i = y0; i < y1; i++) {\
1523 for (j = x0; j < x1; j++) {\
1525 y_pos = (i * 4 * overlay->width) + j * 4;\
1526 for (k = OFFSET; k < 3+OFFSET; k++) {\
1527 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1528 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1533 ARGB_SHADE_FUNCTION (ARGB, 1);
1534 ARGB_SHADE_FUNCTION (ABGR, 1);
1535 ARGB_SHADE_FUNCTION (RGBA, 0);
1536 ARGB_SHADE_FUNCTION (BGRA, 0);
1539 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1540 const gchar * text, gint textlen)
1544 if (!overlay->need_render) {
1545 GST_DEBUG ("Using previously rendered text.");
1549 /* -1 is the whole string */
1550 if (text != NULL && textlen < 0) {
1551 textlen = strlen (text);
1555 string = g_strndup (text, textlen);
1556 } else { /* empty string */
1557 string = g_strdup (" ");
1559 g_strdelimit (string, "\r\t", ' ');
1560 textlen = strlen (string);
1562 /* FIXME: should we check for UTF-8 here? */
1564 GST_DEBUG ("Rendering '%s'", string);
1565 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1569 overlay->need_render = FALSE;
1572 static GstFlowReturn
1573 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1574 GstBuffer * video_frame)
1577 GstVideoFrame frame;
1579 if (overlay->composition == NULL)
1582 if (gst_pad_check_reconfigure (overlay->srcpad))
1583 gst_base_text_overlay_negotiate (overlay);
1585 video_frame = gst_buffer_make_writable (video_frame);
1587 if (overlay->attach_compo_to_buffer) {
1588 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1589 gst_buffer_add_video_overlay_composition_meta (video_frame,
1590 overlay->composition);
1591 /* FIXME: emulate shaded background box if want_shading=true */
1595 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1599 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1601 /* shaded background box */
1602 if (overlay->want_shading) {
1603 switch (overlay->format) {
1604 case GST_VIDEO_FORMAT_I420:
1605 case GST_VIDEO_FORMAT_NV12:
1606 case GST_VIDEO_FORMAT_NV21:
1607 gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1608 xpos, xpos + overlay->image_width,
1609 ypos, ypos + overlay->image_height);
1611 case GST_VIDEO_FORMAT_AYUV:
1612 case GST_VIDEO_FORMAT_UYVY:
1613 gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1614 xpos, xpos + overlay->image_width,
1615 ypos, ypos + overlay->image_height);
1617 case GST_VIDEO_FORMAT_xRGB:
1618 gst_base_text_overlay_shade_xRGB (overlay, &frame,
1619 xpos, xpos + overlay->image_width,
1620 ypos, ypos + overlay->image_height);
1622 case GST_VIDEO_FORMAT_xBGR:
1623 gst_base_text_overlay_shade_xBGR (overlay, &frame,
1624 xpos, xpos + overlay->image_width,
1625 ypos, ypos + overlay->image_height);
1627 case GST_VIDEO_FORMAT_BGRx:
1628 gst_base_text_overlay_shade_BGRx (overlay, &frame,
1629 xpos, xpos + overlay->image_width,
1630 ypos, ypos + overlay->image_height);
1632 case GST_VIDEO_FORMAT_RGBx:
1633 gst_base_text_overlay_shade_RGBx (overlay, &frame,
1634 xpos, xpos + overlay->image_width,
1635 ypos, ypos + overlay->image_height);
1637 case GST_VIDEO_FORMAT_ARGB:
1638 gst_base_text_overlay_shade_ARGB (overlay, &frame,
1639 xpos, xpos + overlay->image_width,
1640 ypos, ypos + overlay->image_height);
1642 case GST_VIDEO_FORMAT_ABGR:
1643 gst_base_text_overlay_shade_ABGR (overlay, &frame,
1644 xpos, xpos + overlay->image_width,
1645 ypos, ypos + overlay->image_height);
1647 case GST_VIDEO_FORMAT_RGBA:
1648 gst_base_text_overlay_shade_RGBA (overlay, &frame,
1649 xpos, xpos + overlay->image_width,
1650 ypos, ypos + overlay->image_height);
1652 case GST_VIDEO_FORMAT_BGRA:
1653 gst_base_text_overlay_shade_BGRA (overlay, &frame,
1654 xpos, xpos + overlay->image_width,
1655 ypos, ypos + overlay->image_height);
1658 g_assert_not_reached ();
1662 gst_video_overlay_composition_blend (overlay->composition, &frame);
1664 gst_video_frame_unmap (&frame);
1668 return gst_pad_push (overlay->srcpad, video_frame);
1673 gst_buffer_unref (video_frame);
1674 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1679 static GstPadLinkReturn
1680 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1682 GstBaseTextOverlay *overlay;
1684 overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1685 if (G_UNLIKELY (!overlay))
1686 return GST_PAD_LINK_REFUSED;
1688 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1690 overlay->text_linked = TRUE;
1692 gst_object_unref (overlay);
1694 return GST_PAD_LINK_OK;
1698 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
1700 GstBaseTextOverlay *overlay;
1702 /* don't use gst_pad_get_parent() here, will deadlock */
1703 overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1705 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1707 overlay->text_linked = FALSE;
1709 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1713 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1716 gboolean ret = FALSE;
1717 GstBaseTextOverlay *overlay = NULL;
1719 overlay = GST_BASE_TEXT_OVERLAY (parent);
1721 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1723 switch (GST_EVENT_TYPE (event)) {
1724 case GST_EVENT_CAPS:
1728 gst_event_parse_caps (event, &caps);
1729 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1730 gst_event_unref (event);
1733 case GST_EVENT_SEGMENT:
1735 const GstSegment *segment;
1737 overlay->text_eos = FALSE;
1739 gst_event_parse_segment (event, &segment);
1741 if (segment->format == GST_FORMAT_TIME) {
1742 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1743 gst_segment_copy_into (segment, &overlay->text_segment);
1744 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1745 &overlay->text_segment);
1746 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1748 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1749 ("received non-TIME newsegment event on text input"));
1752 gst_event_unref (event);
1755 /* wake up the video chain, it might be waiting for a text buffer or
1756 * a text segment update */
1757 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1758 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1759 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1762 case GST_EVENT_FLUSH_STOP:
1763 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1764 GST_INFO_OBJECT (overlay, "text flush stop");
1765 overlay->text_flushing = FALSE;
1766 overlay->text_eos = FALSE;
1767 gst_base_text_overlay_pop_text (overlay);
1768 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1769 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1770 gst_event_unref (event);
1773 case GST_EVENT_FLUSH_START:
1774 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1775 GST_INFO_OBJECT (overlay, "text flush start");
1776 overlay->text_flushing = TRUE;
1777 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1778 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1779 gst_event_unref (event);
1783 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1784 overlay->text_eos = TRUE;
1785 GST_INFO_OBJECT (overlay, "text EOS");
1786 /* wake up the video chain, it might be waiting for a text buffer or
1787 * a text segment update */
1788 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1789 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1790 gst_event_unref (event);
1794 ret = gst_pad_event_default (pad, parent, event);
1802 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1805 gboolean ret = FALSE;
1806 GstBaseTextOverlay *overlay = NULL;
1808 overlay = GST_BASE_TEXT_OVERLAY (parent);
1810 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1812 switch (GST_EVENT_TYPE (event)) {
1813 case GST_EVENT_CAPS:
1817 gst_event_parse_caps (event, &caps);
1818 ret = gst_base_text_overlay_setcaps (overlay, caps);
1819 gst_event_unref (event);
1822 case GST_EVENT_SEGMENT:
1824 const GstSegment *segment;
1826 GST_DEBUG_OBJECT (overlay, "received new segment");
1828 gst_event_parse_segment (event, &segment);
1830 if (segment->format == GST_FORMAT_TIME) {
1831 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1834 gst_segment_copy_into (segment, &overlay->segment);
1836 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1837 ("received non-TIME newsegment event on video input"));
1840 ret = gst_pad_event_default (pad, parent, event);
1844 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1845 GST_INFO_OBJECT (overlay, "video EOS");
1846 overlay->video_eos = TRUE;
1847 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1848 ret = gst_pad_event_default (pad, parent, event);
1850 case GST_EVENT_FLUSH_START:
1851 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1852 GST_INFO_OBJECT (overlay, "video flush start");
1853 overlay->video_flushing = TRUE;
1854 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1855 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1856 ret = gst_pad_event_default (pad, parent, event);
1858 case GST_EVENT_FLUSH_STOP:
1859 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1860 GST_INFO_OBJECT (overlay, "video flush stop");
1861 overlay->video_flushing = FALSE;
1862 overlay->video_eos = FALSE;
1863 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1864 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1865 ret = gst_pad_event_default (pad, parent, event);
1868 ret = gst_pad_event_default (pad, parent, event);
1876 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1879 gboolean ret = FALSE;
1880 GstBaseTextOverlay *overlay;
1882 overlay = GST_BASE_TEXT_OVERLAY (parent);
1884 switch (GST_QUERY_TYPE (query)) {
1885 case GST_QUERY_CAPS:
1887 GstCaps *filter, *caps;
1889 gst_query_parse_caps (query, &filter);
1890 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1891 gst_query_set_caps_result (query, caps);
1892 gst_caps_unref (caps);
1897 ret = gst_pad_query_default (pad, parent, query);
1904 /* Called with lock held */
1906 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1908 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1910 if (overlay->text_buffer) {
1911 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1912 overlay->text_buffer);
1913 gst_buffer_unref (overlay->text_buffer);
1914 overlay->text_buffer = NULL;
1917 /* Let the text task know we used that buffer */
1918 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1921 /* We receive text buffers here. If they are out of segment we just ignore them.
1922 If the buffer is in our segment we keep it internally except if another one
1923 is already waiting here, in that case we wait that it gets kicked out */
1924 static GstFlowReturn
1925 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1928 GstFlowReturn ret = GST_FLOW_OK;
1929 GstBaseTextOverlay *overlay = NULL;
1930 gboolean in_seg = FALSE;
1931 guint64 clip_start = 0, clip_stop = 0;
1933 overlay = GST_BASE_TEXT_OVERLAY (parent);
1935 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1937 if (overlay->text_flushing) {
1938 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1939 ret = GST_FLOW_FLUSHING;
1940 GST_LOG_OBJECT (overlay, "text flushing");
1944 if (overlay->text_eos) {
1945 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1947 GST_LOG_OBJECT (overlay, "text EOS");
1951 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1952 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1953 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1954 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1955 GST_BUFFER_DURATION (buffer)));
1957 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1960 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1961 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1963 stop = GST_CLOCK_TIME_NONE;
1965 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
1966 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1972 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1973 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1974 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1975 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1977 /* Wait for the previous buffer to go away */
1978 while (overlay->text_buffer != NULL) {
1979 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
1980 GST_DEBUG_PAD_NAME (pad));
1981 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
1982 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
1983 if (overlay->text_flushing) {
1984 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1985 ret = GST_FLOW_FLUSHING;
1990 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1991 overlay->text_segment.position = clip_start;
1993 overlay->text_buffer = buffer;
1994 /* That's a new text buffer we need to render */
1995 overlay->need_render = TRUE;
1997 /* in case the video chain is waiting for a text buffer, wake it up */
1998 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2001 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2008 static GstFlowReturn
2009 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2012 GstBaseTextOverlayClass *klass;
2013 GstBaseTextOverlay *overlay;
2014 GstFlowReturn ret = GST_FLOW_OK;
2015 gboolean in_seg = FALSE;
2016 guint64 start, stop, clip_start = 0, clip_stop = 0;
2019 overlay = GST_BASE_TEXT_OVERLAY (parent);
2020 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2022 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2023 goto missing_timestamp;
2025 /* ignore buffers that are outside of the current segment */
2026 start = GST_BUFFER_TIMESTAMP (buffer);
2028 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2029 stop = GST_CLOCK_TIME_NONE;
2031 stop = start + GST_BUFFER_DURATION (buffer);
2034 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2035 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2036 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2038 /* segment_clip() will adjust start unconditionally to segment_start if
2039 * no stop time is provided, so handle this ourselves */
2040 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2041 goto out_of_segment;
2043 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2044 &clip_start, &clip_stop);
2047 goto out_of_segment;
2049 /* if the buffer is only partially in the segment, fix up stamps */
2050 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2051 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2052 buffer = gst_buffer_make_writable (buffer);
2053 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2055 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2058 /* now, after we've done the clipping, fix up end time if there's no
2059 * duration (we only use those estimated values internally though, we
2060 * don't want to set bogus values on the buffer itself) */
2064 gint fps_num, fps_denom;
2066 /* FIXME, store this in setcaps */
2067 caps = gst_pad_get_current_caps (pad);
2068 s = gst_caps_get_structure (caps, 0);
2069 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2070 fps_num && fps_denom) {
2071 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2072 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2074 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2075 stop = start + 1; /* we need to assume some interval */
2077 gst_caps_unref (caps);
2080 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2084 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2086 if (overlay->video_flushing)
2089 if (overlay->video_eos)
2092 if (overlay->silent) {
2093 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2094 ret = gst_pad_push (overlay->srcpad, buffer);
2096 /* Update position */
2097 overlay->segment.position = clip_start;
2102 /* Text pad not linked, rendering internal text */
2103 if (!overlay->text_linked) {
2104 if (klass->get_text) {
2105 text = klass->get_text (overlay, buffer);
2107 text = g_strdup (overlay->default_text);
2110 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2111 "text: '%s'", GST_STR_NULL (text));
2113 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2115 if (text != NULL && *text != '\0') {
2116 /* Render and push */
2117 gst_base_text_overlay_render_text (overlay, text, -1);
2118 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2120 /* Invalid or empty string */
2121 ret = gst_pad_push (overlay->srcpad, buffer);
2124 /* Text pad linked, check if we have a text buffer queued */
2125 if (overlay->text_buffer) {
2126 gboolean pop_text = FALSE, valid_text_time = TRUE;
2127 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2128 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2129 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2130 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2131 GstClockTime vid_running_time, vid_running_time_end;
2133 /* if the text buffer isn't stamped right, pop it off the
2134 * queue and display it for the current video frame only */
2135 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2136 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2137 GST_WARNING_OBJECT (overlay,
2138 "Got text buffer with invalid timestamp or duration");
2140 valid_text_time = FALSE;
2142 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2143 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2147 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2149 vid_running_time_end =
2150 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2153 /* If timestamp and duration are valid */
2154 if (valid_text_time) {
2156 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2158 text_running_time_end =
2159 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2163 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2164 GST_TIME_ARGS (text_running_time),
2165 GST_TIME_ARGS (text_running_time_end));
2166 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2167 GST_TIME_ARGS (vid_running_time),
2168 GST_TIME_ARGS (vid_running_time_end));
2170 /* Text too old or in the future */
2171 if (valid_text_time && text_running_time_end <= vid_running_time) {
2172 /* text buffer too old, get rid of it and do nothing */
2173 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2175 gst_base_text_overlay_pop_text (overlay);
2176 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2177 goto wait_for_text_buf;
2178 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2179 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2180 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2181 /* Push the video frame */
2182 ret = gst_pad_push (overlay->srcpad, buffer);
2188 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2189 in_text = (gchar *) map.data;
2192 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2193 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2194 * here on purpose, this is something that needs fixing upstream */
2195 if (!g_utf8_validate (in_text, in_size, NULL)) {
2196 const gchar *end = NULL;
2198 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2199 in_text = g_strndup (in_text, in_size);
2200 while (!g_utf8_validate (in_text, in_size, &end) && end)
2201 *((gchar *) end) = '*';
2204 /* Get the string */
2205 if (overlay->have_pango_markup) {
2206 text = g_strndup (in_text, in_size);
2208 text = g_markup_escape_text (in_text, in_size);
2211 if (text != NULL && *text != '\0') {
2212 gint text_len = strlen (text);
2214 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2215 text[text_len - 1] == '\r')) {
2218 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2219 gst_base_text_overlay_render_text (overlay, text, text_len);
2221 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2222 gst_base_text_overlay_render_text (overlay, " ", 1);
2224 if (in_text != (gchar *) map.data)
2227 gst_buffer_unmap (overlay->text_buffer, &map);
2229 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2230 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2232 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2233 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2238 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2239 gst_base_text_overlay_pop_text (overlay);
2240 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2243 gboolean wait_for_text_buf = TRUE;
2245 if (overlay->text_eos)
2246 wait_for_text_buf = FALSE;
2248 if (!overlay->wait_text)
2249 wait_for_text_buf = FALSE;
2251 /* Text pad linked, but no text buffer available - what now? */
2252 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2253 GstClockTime text_start_running_time, text_position_running_time;
2254 GstClockTime vid_running_time;
2257 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2258 GST_BUFFER_TIMESTAMP (buffer));
2259 text_start_running_time =
2260 gst_segment_to_running_time (&overlay->text_segment,
2261 GST_FORMAT_TIME, overlay->text_segment.start);
2262 text_position_running_time =
2263 gst_segment_to_running_time (&overlay->text_segment,
2264 GST_FORMAT_TIME, overlay->text_segment.position);
2266 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2267 vid_running_time < text_start_running_time) ||
2268 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2269 vid_running_time < text_position_running_time)) {
2270 wait_for_text_buf = FALSE;
2274 if (wait_for_text_buf) {
2275 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2276 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2277 GST_DEBUG_OBJECT (overlay, "resuming");
2278 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2279 goto wait_for_text_buf;
2281 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2282 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2283 ret = gst_pad_push (overlay->srcpad, buffer);
2290 /* Update position */
2291 overlay->segment.position = clip_start;
2297 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2298 gst_buffer_unref (buffer);
2304 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2305 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2306 gst_buffer_unref (buffer);
2307 return GST_FLOW_FLUSHING;
2311 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2312 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2313 gst_buffer_unref (buffer);
2314 return GST_FLOW_EOS;
2318 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2319 gst_buffer_unref (buffer);
2324 static GstStateChangeReturn
2325 gst_base_text_overlay_change_state (GstElement * element,
2326 GstStateChange transition)
2328 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2329 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2331 switch (transition) {
2332 case GST_STATE_CHANGE_PAUSED_TO_READY:
2333 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2334 overlay->text_flushing = TRUE;
2335 overlay->video_flushing = TRUE;
2336 /* pop_text will broadcast on the GCond and thus also make the video
2337 * chain exit if it's waiting for a text buffer */
2338 gst_base_text_overlay_pop_text (overlay);
2339 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2345 ret = parent_class->change_state (element, transition);
2346 if (ret == GST_STATE_CHANGE_FAILURE)
2349 switch (transition) {
2350 case GST_STATE_CHANGE_READY_TO_PAUSED:
2351 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2352 overlay->text_flushing = FALSE;
2353 overlay->video_flushing = FALSE;
2354 overlay->video_eos = FALSE;
2355 overlay->text_eos = FALSE;
2356 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2357 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2358 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2368 plugin_init (GstPlugin * plugin)
2370 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2371 GST_TYPE_TEXT_OVERLAY) ||
2372 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2373 GST_TYPE_TIME_OVERLAY) ||
2374 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2375 GST_TYPE_CLOCK_OVERLAY) ||
2376 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2377 GST_TYPE_TEXT_RENDER)) {
2381 /*texttestsrc_plugin_init(module, plugin); */
2383 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2388 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2389 pango, "Pango-based text rendering and overlay", plugin_init,
2390 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)