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>
79 #include <gst/video/gstvideometa.h>
81 #include "gstbasetextoverlay.h"
82 #include "gsttextoverlay.h"
83 #include "gsttimeoverlay.h"
84 #include "gstclockoverlay.h"
85 #include "gsttextrender.h"
89 * - use proper strides and offset for I420
90 * - if text is wider than the video picture, it does not get
91 * clipped properly during blitting (if wrapping is disabled)
92 * - make 'shading_value' a property (or enum: light/normal/dark/verydark)?
95 GST_DEBUG_CATEGORY (pango_debug);
96 #define GST_CAT_DEFAULT pango_debug
98 #define DEFAULT_PROP_TEXT ""
99 #define DEFAULT_PROP_SHADING FALSE
100 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
101 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
102 #define DEFAULT_PROP_XPAD 25
103 #define DEFAULT_PROP_YPAD 25
104 #define DEFAULT_PROP_DELTAX 0
105 #define DEFAULT_PROP_DELTAY 0
106 #define DEFAULT_PROP_XPOS 0.5
107 #define DEFAULT_PROP_YPOS 0.5
108 #define DEFAULT_PROP_WRAP_MODE GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
109 #define DEFAULT_PROP_FONT_DESC ""
110 #define DEFAULT_PROP_SILENT FALSE
111 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
112 #define DEFAULT_PROP_WAIT_TEXT TRUE
113 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
114 #define DEFAULT_PROP_VERTICAL_RENDER FALSE
115 #define DEFAULT_PROP_COLOR 0xffffffff
116 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
118 /* make a property of me */
119 #define DEFAULT_SHADING_VALUE -80
121 #define MINIMUM_OUTLINE_OFFSET 1.0
122 #define DEFAULT_SCALE_BASIS 640
124 #define COMP_Y(ret, r, g, b) \
126 ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
127 ret = CLAMP (ret, 0, 255); \
130 #define COMP_U(ret, r, g, b) \
132 ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
133 ret = CLAMP (ret, 0, 255); \
136 #define COMP_V(ret, r, g, b) \
138 ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
139 ret = CLAMP (ret, 0, 255); \
142 #define BLEND(ret, alpha, v0, v1) \
144 ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
147 #define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew) \
150 _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \
151 ret = CLAMP (_tmp, 0, 255); \
154 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
155 # define CAIRO_ARGB_A 3
156 # define CAIRO_ARGB_R 2
157 # define CAIRO_ARGB_G 1
158 # define CAIRO_ARGB_B 0
160 # define CAIRO_ARGB_A 0
161 # define CAIRO_ARGB_R 1
162 # define CAIRO_ARGB_G 2
163 # define CAIRO_ARGB_B 3
184 PROP_AUTO_ADJUST_SIZE,
185 PROP_VERTICAL_RENDER,
192 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
193 I420, YV12, AYUV, YUY2, UYVY, v308, v210, v216, Y41B, Y42B, Y444, \
194 Y800, Y16, NV12, NV21, UYVP, A420, YUV9, IYU1 }"
196 static GstStaticPadTemplate src_template_factory =
197 GST_STATIC_PAD_TEMPLATE ("src",
200 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
203 static GstStaticPadTemplate video_sink_template_factory =
204 GST_STATIC_PAD_TEMPLATE ("video_sink",
207 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
210 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
212 gst_base_text_overlay_valign_get_type (void)
214 static GType base_text_overlay_valign_type = 0;
215 static const GEnumValue base_text_overlay_valign[] = {
216 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
217 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
218 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
219 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
220 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
224 if (!base_text_overlay_valign_type) {
225 base_text_overlay_valign_type =
226 g_enum_register_static ("GstBaseTextOverlayVAlign",
227 base_text_overlay_valign);
229 return base_text_overlay_valign_type;
232 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
234 gst_base_text_overlay_halign_get_type (void)
236 static GType base_text_overlay_halign_type = 0;
237 static const GEnumValue base_text_overlay_halign[] = {
238 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
239 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
240 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
241 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
245 if (!base_text_overlay_halign_type) {
246 base_text_overlay_halign_type =
247 g_enum_register_static ("GstBaseTextOverlayHAlign",
248 base_text_overlay_halign);
250 return base_text_overlay_halign_type;
254 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
256 gst_base_text_overlay_wrap_mode_get_type (void)
258 static GType base_text_overlay_wrap_mode_type = 0;
259 static const GEnumValue base_text_overlay_wrap_mode[] = {
260 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
261 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
262 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
263 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
267 if (!base_text_overlay_wrap_mode_type) {
268 base_text_overlay_wrap_mode_type =
269 g_enum_register_static ("GstBaseTextOverlayWrapMode",
270 base_text_overlay_wrap_mode);
272 return base_text_overlay_wrap_mode_type;
275 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
277 gst_base_text_overlay_line_align_get_type (void)
279 static GType base_text_overlay_line_align_type = 0;
280 static const GEnumValue base_text_overlay_line_align[] = {
281 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
282 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
283 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
287 if (!base_text_overlay_line_align_type) {
288 base_text_overlay_line_align_type =
289 g_enum_register_static ("GstBaseTextOverlayLineAlign",
290 base_text_overlay_line_align);
292 return base_text_overlay_line_align_type;
295 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
296 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
297 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
298 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
299 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
300 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
301 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
303 static GstElementClass *parent_class = NULL;
304 static void gst_base_text_overlay_base_init (gpointer g_class);
305 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
306 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
307 GstBaseTextOverlayClass * klass);
309 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
310 element, GstStateChange transition);
312 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
313 GstBaseTextOverlay * overlay, GstCaps * filter);
314 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
316 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
318 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
319 GstObject * parent, GstEvent * event);
320 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
321 GstObject * parent, GstQuery * query);
323 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
324 GstObject * parent, GstEvent * event);
325 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
326 GstObject * parent, GstQuery * query);
327 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
328 GstObject * parent, GstBuffer * buffer);
330 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
331 GstObject * parent, GstEvent * event);
332 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
333 GstObject * parent, GstBuffer * buffer);
334 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
336 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad);
337 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
338 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
341 static void gst_base_text_overlay_finalize (GObject * object);
342 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
343 const GValue * value, GParamSpec * pspec);
344 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
345 GValue * value, GParamSpec * pspec);
347 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
348 PangoFontDescription * desc);
351 gst_base_text_overlay_get_type (void)
353 static GType type = 0;
355 if (g_once_init_enter ((gsize *) & type)) {
356 static const GTypeInfo info = {
357 sizeof (GstBaseTextOverlayClass),
358 (GBaseInitFunc) gst_base_text_overlay_base_init,
360 (GClassInitFunc) gst_base_text_overlay_class_init,
363 sizeof (GstBaseTextOverlay),
365 (GInstanceInitFunc) gst_base_text_overlay_init,
368 g_once_init_leave ((gsize *) & type,
369 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
377 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
378 GstBuffer * video_frame)
380 return g_strdup (overlay->default_text);
384 gst_base_text_overlay_base_init (gpointer g_class)
386 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
387 PangoFontMap *fontmap;
389 /* Only lock for the subclasses here, the base class
390 * doesn't have this mutex yet and it's not necessary
392 if (klass->pango_lock)
393 g_mutex_lock (klass->pango_lock);
394 fontmap = pango_cairo_font_map_get_default ();
395 klass->pango_context =
396 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
397 if (klass->pango_lock)
398 g_mutex_unlock (klass->pango_lock);
402 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
404 GObjectClass *gobject_class;
405 GstElementClass *gstelement_class;
407 gobject_class = (GObjectClass *) klass;
408 gstelement_class = (GstElementClass *) klass;
410 parent_class = g_type_class_peek_parent (klass);
412 gobject_class->finalize = gst_base_text_overlay_finalize;
413 gobject_class->set_property = gst_base_text_overlay_set_property;
414 gobject_class->get_property = gst_base_text_overlay_get_property;
416 gst_element_class_add_pad_template (gstelement_class,
417 gst_static_pad_template_get (&src_template_factory));
418 gst_element_class_add_pad_template (gstelement_class,
419 gst_static_pad_template_get (&video_sink_template_factory));
421 gstelement_class->change_state =
422 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
424 klass->pango_lock = g_slice_new (GMutex);
425 g_mutex_init (klass->pango_lock);
427 klass->get_text = gst_base_text_overlay_get_text;
429 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
430 g_param_spec_string ("text", "text",
431 "Text to be display.", DEFAULT_PROP_TEXT,
432 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
433 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
434 g_param_spec_boolean ("shaded-background", "shaded background",
435 "Whether to shade the background under the text area",
436 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
437 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
438 g_param_spec_enum ("valignment", "vertical alignment",
439 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
440 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
441 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
442 g_param_spec_enum ("halignment", "horizontal alignment",
443 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
444 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
445 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
446 g_param_spec_int ("xpad", "horizontal paddding",
447 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
448 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
449 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
450 g_param_spec_int ("ypad", "vertical padding",
451 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
452 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
453 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
454 g_param_spec_int ("deltax", "X position modifier",
455 "Shift X position to the left or to the right. Unit is pixels.",
456 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
457 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
458 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
459 g_param_spec_int ("deltay", "Y position modifier",
460 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
461 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
463 * GstBaseTextOverlay:xpos
465 * Horizontal position of the rendered text when using positioned alignment.
469 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
470 g_param_spec_double ("xpos", "horizontal position",
471 "Horizontal position when using position alignment", 0, 1.0,
473 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
475 * GstBaseTextOverlay:ypos
477 * Vertical position of the rendered text when using positioned alignment.
481 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
482 g_param_spec_double ("ypos", "vertical position",
483 "Vertical position when using position alignment", 0, 1.0,
485 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
486 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
487 g_param_spec_enum ("wrap-mode", "wrap mode",
488 "Whether to wrap the text and if so how.",
489 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
490 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
491 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
492 g_param_spec_string ("font-desc", "font description",
493 "Pango font description of font to be used for rendering. "
494 "See documentation of pango_font_description_from_string "
495 "for syntax.", DEFAULT_PROP_FONT_DESC,
496 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
498 * GstBaseTextOverlay:color
500 * Color of the rendered text.
504 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
505 g_param_spec_uint ("color", "Color",
506 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
508 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
510 * GstTextOverlay:outline-color
512 * Color of the outline of the rendered text.
516 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
517 g_param_spec_uint ("outline-color", "Text Outline Color",
518 "Color to use for outline the text (big-endian ARGB).", 0,
519 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
520 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
523 * GstBaseTextOverlay:line-alignment
525 * Alignment of text lines relative to each other (for multi-line text)
529 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
530 g_param_spec_enum ("line-alignment", "line alignment",
531 "Alignment of text lines relative to each other.",
532 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
533 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
535 * GstBaseTextOverlay:silent
537 * If set, no text is rendered. Useful to switch off text rendering
538 * temporarily without removing the textoverlay element from the pipeline.
542 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
543 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
544 g_param_spec_boolean ("silent", "silent",
545 "Whether to render the text string",
547 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
549 * GstBaseTextOverlay:wait-text
551 * If set, the video will block until a subtitle is received on the text pad.
552 * If video and subtitles are sent in sync, like from the same demuxer, this
553 * property should be set.
557 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
558 g_param_spec_boolean ("wait-text", "Wait Text",
559 "Whether to wait for subtitles",
560 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
562 g_object_class_install_property (G_OBJECT_CLASS (klass),
563 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
564 "Automatically adjust font size to screen-size.",
565 DEFAULT_PROP_AUTO_ADJUST_SIZE,
566 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
568 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
569 g_param_spec_boolean ("vertical-render", "vertical render",
570 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
571 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
575 gst_base_text_overlay_finalize (GObject * object)
577 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
579 g_free (overlay->default_text);
581 if (overlay->composition) {
582 gst_video_overlay_composition_unref (overlay->composition);
583 overlay->composition = NULL;
586 if (overlay->text_image) {
587 gst_buffer_unref (overlay->text_image);
588 overlay->text_image = NULL;
591 if (overlay->layout) {
592 g_object_unref (overlay->layout);
593 overlay->layout = NULL;
596 if (overlay->text_buffer) {
597 gst_buffer_unref (overlay->text_buffer);
598 overlay->text_buffer = NULL;
601 g_mutex_clear (&overlay->lock);
602 g_cond_clear (&overlay->cond);
604 G_OBJECT_CLASS (parent_class)->finalize (object);
608 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
609 GstBaseTextOverlayClass * klass)
611 GstPadTemplate *template;
612 PangoFontDescription *desc;
615 template = gst_static_pad_template_get (&video_sink_template_factory);
616 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
617 gst_object_unref (template);
618 gst_pad_set_event_function (overlay->video_sinkpad,
619 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
620 gst_pad_set_chain_function (overlay->video_sinkpad,
621 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
622 gst_pad_set_query_function (overlay->video_sinkpad,
623 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
624 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
627 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
631 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
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 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1236 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1237 overlay->image_width, overlay->image_height);
1238 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1239 xpos, ypos, overlay->image_width, overlay->image_height,
1240 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1242 if (overlay->composition)
1243 gst_video_overlay_composition_unref (overlay->composition);
1244 overlay->composition = gst_video_overlay_composition_new (rectangle);
1245 gst_video_overlay_rectangle_unref (rectangle);
1247 } else if (overlay->composition) {
1248 gst_video_overlay_composition_unref (overlay->composition);
1249 overlay->composition = NULL;
1254 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1255 const gchar * string, gint textlen)
1258 cairo_surface_t *surface;
1259 PangoRectangle ink_rect, logical_rect;
1260 cairo_matrix_t cairo_matrix;
1262 double scalef = 1.0;
1267 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1269 if (overlay->auto_adjust_size) {
1270 /* 640 pixel is default */
1271 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1273 pango_layout_set_width (overlay->layout, -1);
1274 /* set text on pango layout */
1275 pango_layout_set_markup (overlay->layout, string, textlen);
1277 /* get subtitle image size */
1278 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1280 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1282 if (width + overlay->deltax >
1283 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1285 * subtitle image width is larger then overlay width
1286 * so rearrange overlay wrap mode.
1288 gst_base_text_overlay_update_wrap_mode (overlay);
1289 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1290 width = overlay->width;
1294 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1295 if (height > overlay->height) {
1296 height = overlay->height;
1298 if (overlay->use_vertical_render) {
1299 PangoRectangle rect;
1300 PangoContext *context;
1301 PangoMatrix matrix = PANGO_MATRIX_INIT;
1304 context = pango_layout_get_context (overlay->layout);
1306 pango_matrix_rotate (&matrix, -90);
1308 rect.x = rect.y = 0;
1310 rect.height = height;
1311 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1312 matrix.x0 = -rect.x;
1313 matrix.y0 = -rect.y;
1315 pango_context_set_matrix (context, &matrix);
1317 cairo_matrix.xx = matrix.xx;
1318 cairo_matrix.yx = matrix.yx;
1319 cairo_matrix.xy = matrix.xy;
1320 cairo_matrix.yy = matrix.yy;
1321 cairo_matrix.x0 = matrix.x0;
1322 cairo_matrix.y0 = matrix.y0;
1323 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1329 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1332 /* reallocate overlay buffer */
1333 buffer = gst_buffer_new_and_alloc (4 * width * height);
1334 gst_buffer_replace (&overlay->text_image, buffer);
1335 gst_buffer_unref (buffer);
1337 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1338 surface = cairo_image_surface_create_for_data (map.data,
1339 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1340 cr = cairo_create (surface);
1343 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1346 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1348 if (overlay->want_shading)
1349 cairo_paint_with_alpha (cr, overlay->shading_value);
1351 /* apply transformations */
1352 cairo_set_matrix (cr, &cairo_matrix);
1354 /* FIXME: We use show_layout everywhere except for the surface
1355 * because it's really faster and internally does all kinds of
1356 * caching. Unfortunately we have to paint to a cairo path for
1357 * the outline and this is slow. Once Pango supports user fonts
1358 * we should use them, see
1359 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1361 * Idea would the be, to create a cairo user font that
1362 * does shadow, outline, text painting in the
1363 * render_glyph function.
1366 /* draw shadow text */
1368 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1369 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1370 pango_cairo_show_layout (cr, overlay->layout);
1373 a = (overlay->outline_color >> 24) & 0xff;
1374 r = (overlay->outline_color >> 16) & 0xff;
1375 g = (overlay->outline_color >> 8) & 0xff;
1376 b = (overlay->outline_color >> 0) & 0xff;
1378 /* draw outline text */
1380 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1381 cairo_set_line_width (cr, overlay->outline_offset);
1382 pango_cairo_layout_path (cr, overlay->layout);
1386 a = (overlay->color >> 24) & 0xff;
1387 r = (overlay->color >> 16) & 0xff;
1388 g = (overlay->color >> 8) & 0xff;
1389 b = (overlay->color >> 0) & 0xff;
1393 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1394 pango_cairo_show_layout (cr, overlay->layout);
1398 cairo_surface_destroy (surface);
1399 gst_buffer_unmap (buffer, &map);
1400 overlay->image_width = width;
1401 overlay->image_height = height;
1402 overlay->baseline_y = ink_rect.y;
1403 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1405 gst_base_text_overlay_set_composition (overlay);
1412 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1413 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1415 gint i, j, dest_stride;
1418 dest_stride = dest->info.stride[0];
1419 dest_ptr = dest->data[0];
1421 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1422 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1424 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1425 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1427 for (i = y0; i < y1; ++i) {
1428 for (j = x0; j < x1; ++j) {
1429 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1431 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1437 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1438 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1441 guint dest_stride, pixel_stride;
1444 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1445 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1446 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1448 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1449 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1451 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1452 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1455 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1457 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1460 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1462 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1464 for (i = y0; i < y1; i++) {
1465 for (j = x0; j < x1; j++) {
1469 y_pos = (i * dest_stride) + j * pixel_stride;
1470 y = dest_ptr[y_pos] + overlay->shading_value;
1472 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1477 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1478 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1479 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1481 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1482 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1487 dest_ptr = dest->data[0];
1489 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1490 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1492 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1493 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1495 for (i = y0; i < y1; i++) {
1496 for (j = x0; j < x1; j++) {
1499 y_pos = (i * 4 * overlay->width) + j * 4;
1500 for (k = 0; k < 4; k++) {
1501 y = dest_ptr[y_pos + k] + overlay->shading_value;
1502 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1508 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1509 static inline void \
1510 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1511 gint x0, gint x1, gint y0, gint y1) \
1516 dest_ptr = dest->data[0];\
1518 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1519 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1521 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1522 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1524 for (i = y0; i < y1; i++) {\
1525 for (j = x0; j < x1; j++) {\
1527 y_pos = (i * 4 * overlay->width) + j * 4;\
1528 for (k = OFFSET; k < 3+OFFSET; k++) {\
1529 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1530 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1535 ARGB_SHADE_FUNCTION (ARGB, 1);
1536 ARGB_SHADE_FUNCTION (ABGR, 1);
1537 ARGB_SHADE_FUNCTION (RGBA, 0);
1538 ARGB_SHADE_FUNCTION (BGRA, 0);
1541 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1542 const gchar * text, gint textlen)
1546 if (!overlay->need_render) {
1547 GST_DEBUG ("Using previously rendered text.");
1551 /* -1 is the whole string */
1552 if (text != NULL && textlen < 0) {
1553 textlen = strlen (text);
1557 string = g_strndup (text, textlen);
1558 } else { /* empty string */
1559 string = g_strdup (" ");
1561 g_strdelimit (string, "\r\t", ' ');
1562 textlen = strlen (string);
1564 /* FIXME: should we check for UTF-8 here? */
1566 GST_DEBUG ("Rendering '%s'", string);
1567 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1571 overlay->need_render = FALSE;
1574 static GstFlowReturn
1575 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1576 GstBuffer * video_frame)
1579 GstVideoFrame frame;
1581 if (overlay->composition == NULL)
1584 if (gst_pad_check_reconfigure (overlay->srcpad))
1585 gst_base_text_overlay_negotiate (overlay);
1587 video_frame = gst_buffer_make_writable (video_frame);
1589 if (overlay->attach_compo_to_buffer) {
1590 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1591 gst_buffer_add_video_overlay_composition_meta (video_frame,
1592 overlay->composition);
1593 /* FIXME: emulate shaded background box if want_shading=true */
1597 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1601 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1603 /* shaded background box */
1604 if (overlay->want_shading) {
1605 switch (overlay->format) {
1606 case GST_VIDEO_FORMAT_I420:
1607 case GST_VIDEO_FORMAT_NV12:
1608 case GST_VIDEO_FORMAT_NV21:
1609 gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1610 xpos, xpos + overlay->image_width,
1611 ypos, ypos + overlay->image_height);
1613 case GST_VIDEO_FORMAT_AYUV:
1614 case GST_VIDEO_FORMAT_UYVY:
1615 gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1616 xpos, xpos + overlay->image_width,
1617 ypos, ypos + overlay->image_height);
1619 case GST_VIDEO_FORMAT_xRGB:
1620 gst_base_text_overlay_shade_xRGB (overlay, &frame,
1621 xpos, xpos + overlay->image_width,
1622 ypos, ypos + overlay->image_height);
1624 case GST_VIDEO_FORMAT_xBGR:
1625 gst_base_text_overlay_shade_xBGR (overlay, &frame,
1626 xpos, xpos + overlay->image_width,
1627 ypos, ypos + overlay->image_height);
1629 case GST_VIDEO_FORMAT_BGRx:
1630 gst_base_text_overlay_shade_BGRx (overlay, &frame,
1631 xpos, xpos + overlay->image_width,
1632 ypos, ypos + overlay->image_height);
1634 case GST_VIDEO_FORMAT_RGBx:
1635 gst_base_text_overlay_shade_RGBx (overlay, &frame,
1636 xpos, xpos + overlay->image_width,
1637 ypos, ypos + overlay->image_height);
1639 case GST_VIDEO_FORMAT_ARGB:
1640 gst_base_text_overlay_shade_ARGB (overlay, &frame,
1641 xpos, xpos + overlay->image_width,
1642 ypos, ypos + overlay->image_height);
1644 case GST_VIDEO_FORMAT_ABGR:
1645 gst_base_text_overlay_shade_ABGR (overlay, &frame,
1646 xpos, xpos + overlay->image_width,
1647 ypos, ypos + overlay->image_height);
1649 case GST_VIDEO_FORMAT_RGBA:
1650 gst_base_text_overlay_shade_RGBA (overlay, &frame,
1651 xpos, xpos + overlay->image_width,
1652 ypos, ypos + overlay->image_height);
1654 case GST_VIDEO_FORMAT_BGRA:
1655 gst_base_text_overlay_shade_BGRA (overlay, &frame,
1656 xpos, xpos + overlay->image_width,
1657 ypos, ypos + overlay->image_height);
1660 g_assert_not_reached ();
1664 gst_video_overlay_composition_blend (overlay->composition, &frame);
1666 gst_video_frame_unmap (&frame);
1670 return gst_pad_push (overlay->srcpad, video_frame);
1675 gst_buffer_unref (video_frame);
1676 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1681 static GstPadLinkReturn
1682 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1684 GstBaseTextOverlay *overlay;
1686 overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1687 if (G_UNLIKELY (!overlay))
1688 return GST_PAD_LINK_REFUSED;
1690 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1692 overlay->text_linked = TRUE;
1694 gst_object_unref (overlay);
1696 return GST_PAD_LINK_OK;
1700 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
1702 GstBaseTextOverlay *overlay;
1704 /* don't use gst_pad_get_parent() here, will deadlock */
1705 overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1707 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1709 overlay->text_linked = FALSE;
1711 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1715 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1718 gboolean ret = FALSE;
1719 GstBaseTextOverlay *overlay = NULL;
1721 overlay = GST_BASE_TEXT_OVERLAY (parent);
1723 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1725 switch (GST_EVENT_TYPE (event)) {
1726 case GST_EVENT_CAPS:
1730 gst_event_parse_caps (event, &caps);
1731 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1732 gst_event_unref (event);
1735 case GST_EVENT_SEGMENT:
1737 const GstSegment *segment;
1739 overlay->text_eos = FALSE;
1741 gst_event_parse_segment (event, &segment);
1743 if (segment->format == GST_FORMAT_TIME) {
1744 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1745 gst_segment_copy_into (segment, &overlay->text_segment);
1746 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1747 &overlay->text_segment);
1748 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1750 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1751 ("received non-TIME newsegment event on text input"));
1754 gst_event_unref (event);
1757 /* wake up the video chain, it might be waiting for a text buffer or
1758 * a text segment update */
1759 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1760 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1761 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1764 case GST_EVENT_FLUSH_STOP:
1765 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1766 GST_INFO_OBJECT (overlay, "text flush stop");
1767 overlay->text_flushing = FALSE;
1768 overlay->text_eos = FALSE;
1769 gst_base_text_overlay_pop_text (overlay);
1770 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1771 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1772 gst_event_unref (event);
1775 case GST_EVENT_FLUSH_START:
1776 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1777 GST_INFO_OBJECT (overlay, "text flush start");
1778 overlay->text_flushing = TRUE;
1779 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1780 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1781 gst_event_unref (event);
1785 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1786 overlay->text_eos = TRUE;
1787 GST_INFO_OBJECT (overlay, "text EOS");
1788 /* wake up the video chain, it might be waiting for a text buffer or
1789 * a text segment update */
1790 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1791 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1792 gst_event_unref (event);
1796 ret = gst_pad_event_default (pad, parent, event);
1804 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1807 gboolean ret = FALSE;
1808 GstBaseTextOverlay *overlay = NULL;
1810 overlay = GST_BASE_TEXT_OVERLAY (parent);
1812 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1814 switch (GST_EVENT_TYPE (event)) {
1815 case GST_EVENT_CAPS:
1819 gst_event_parse_caps (event, &caps);
1820 ret = gst_base_text_overlay_setcaps (overlay, caps);
1821 gst_event_unref (event);
1824 case GST_EVENT_SEGMENT:
1826 const GstSegment *segment;
1828 GST_DEBUG_OBJECT (overlay, "received new segment");
1830 gst_event_parse_segment (event, &segment);
1832 if (segment->format == GST_FORMAT_TIME) {
1833 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1836 gst_segment_copy_into (segment, &overlay->segment);
1838 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1839 ("received non-TIME newsegment event on video input"));
1842 ret = gst_pad_event_default (pad, parent, event);
1846 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1847 GST_INFO_OBJECT (overlay, "video EOS");
1848 overlay->video_eos = TRUE;
1849 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1850 ret = gst_pad_event_default (pad, parent, event);
1852 case GST_EVENT_FLUSH_START:
1853 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1854 GST_INFO_OBJECT (overlay, "video flush start");
1855 overlay->video_flushing = TRUE;
1856 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1857 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1858 ret = gst_pad_event_default (pad, parent, event);
1860 case GST_EVENT_FLUSH_STOP:
1861 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1862 GST_INFO_OBJECT (overlay, "video flush stop");
1863 overlay->video_flushing = FALSE;
1864 overlay->video_eos = FALSE;
1865 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1866 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1867 ret = gst_pad_event_default (pad, parent, event);
1870 ret = gst_pad_event_default (pad, parent, event);
1878 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1881 gboolean ret = FALSE;
1882 GstBaseTextOverlay *overlay;
1884 overlay = GST_BASE_TEXT_OVERLAY (parent);
1886 switch (GST_QUERY_TYPE (query)) {
1887 case GST_QUERY_CAPS:
1889 GstCaps *filter, *caps;
1891 gst_query_parse_caps (query, &filter);
1892 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1893 gst_query_set_caps_result (query, caps);
1894 gst_caps_unref (caps);
1899 ret = gst_pad_query_default (pad, parent, query);
1906 /* Called with lock held */
1908 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1910 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1912 if (overlay->text_buffer) {
1913 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1914 overlay->text_buffer);
1915 gst_buffer_unref (overlay->text_buffer);
1916 overlay->text_buffer = NULL;
1919 /* Let the text task know we used that buffer */
1920 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1923 /* We receive text buffers here. If they are out of segment we just ignore them.
1924 If the buffer is in our segment we keep it internally except if another one
1925 is already waiting here, in that case we wait that it gets kicked out */
1926 static GstFlowReturn
1927 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1930 GstFlowReturn ret = GST_FLOW_OK;
1931 GstBaseTextOverlay *overlay = NULL;
1932 gboolean in_seg = FALSE;
1933 guint64 clip_start = 0, clip_stop = 0;
1935 overlay = GST_BASE_TEXT_OVERLAY (parent);
1937 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1939 if (overlay->text_flushing) {
1940 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1941 ret = GST_FLOW_FLUSHING;
1942 GST_LOG_OBJECT (overlay, "text flushing");
1946 if (overlay->text_eos) {
1947 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1949 GST_LOG_OBJECT (overlay, "text EOS");
1953 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1954 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1955 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1956 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1957 GST_BUFFER_DURATION (buffer)));
1959 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1962 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1963 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1965 stop = GST_CLOCK_TIME_NONE;
1967 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
1968 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1974 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1975 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1976 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1977 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1979 /* Wait for the previous buffer to go away */
1980 while (overlay->text_buffer != NULL) {
1981 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
1982 GST_DEBUG_PAD_NAME (pad));
1983 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
1984 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
1985 if (overlay->text_flushing) {
1986 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1987 ret = GST_FLOW_FLUSHING;
1992 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1993 overlay->text_segment.position = clip_start;
1995 overlay->text_buffer = buffer;
1996 /* That's a new text buffer we need to render */
1997 overlay->need_render = TRUE;
1999 /* in case the video chain is waiting for a text buffer, wake it up */
2000 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2003 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2010 static GstFlowReturn
2011 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2014 GstBaseTextOverlayClass *klass;
2015 GstBaseTextOverlay *overlay;
2016 GstFlowReturn ret = GST_FLOW_OK;
2017 gboolean in_seg = FALSE;
2018 guint64 start, stop, clip_start = 0, clip_stop = 0;
2021 overlay = GST_BASE_TEXT_OVERLAY (parent);
2022 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2024 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2025 goto missing_timestamp;
2027 /* ignore buffers that are outside of the current segment */
2028 start = GST_BUFFER_TIMESTAMP (buffer);
2030 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2031 stop = GST_CLOCK_TIME_NONE;
2033 stop = start + GST_BUFFER_DURATION (buffer);
2036 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2037 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2038 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2040 /* segment_clip() will adjust start unconditionally to segment_start if
2041 * no stop time is provided, so handle this ourselves */
2042 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2043 goto out_of_segment;
2045 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2046 &clip_start, &clip_stop);
2049 goto out_of_segment;
2051 /* if the buffer is only partially in the segment, fix up stamps */
2052 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2053 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2054 buffer = gst_buffer_make_writable (buffer);
2055 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2057 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2060 /* now, after we've done the clipping, fix up end time if there's no
2061 * duration (we only use those estimated values internally though, we
2062 * don't want to set bogus values on the buffer itself) */
2066 gint fps_num, fps_denom;
2068 /* FIXME, store this in setcaps */
2069 caps = gst_pad_get_current_caps (pad);
2070 s = gst_caps_get_structure (caps, 0);
2071 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2072 fps_num && fps_denom) {
2073 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2074 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2076 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2077 stop = start + 1; /* we need to assume some interval */
2079 gst_caps_unref (caps);
2082 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2086 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2088 if (overlay->video_flushing)
2091 if (overlay->video_eos)
2094 if (overlay->silent) {
2095 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2096 ret = gst_pad_push (overlay->srcpad, buffer);
2098 /* Update position */
2099 overlay->segment.position = clip_start;
2104 /* Text pad not linked, rendering internal text */
2105 if (!overlay->text_linked) {
2106 if (klass->get_text) {
2107 text = klass->get_text (overlay, buffer);
2109 text = g_strdup (overlay->default_text);
2112 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2113 "text: '%s'", GST_STR_NULL (text));
2115 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2117 if (text != NULL && *text != '\0') {
2118 /* Render and push */
2119 gst_base_text_overlay_render_text (overlay, text, -1);
2120 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2122 /* Invalid or empty string */
2123 ret = gst_pad_push (overlay->srcpad, buffer);
2126 /* Text pad linked, check if we have a text buffer queued */
2127 if (overlay->text_buffer) {
2128 gboolean pop_text = FALSE, valid_text_time = TRUE;
2129 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2130 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2131 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2132 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2133 GstClockTime vid_running_time, vid_running_time_end;
2135 /* if the text buffer isn't stamped right, pop it off the
2136 * queue and display it for the current video frame only */
2137 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2138 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2139 GST_WARNING_OBJECT (overlay,
2140 "Got text buffer with invalid timestamp or duration");
2142 valid_text_time = FALSE;
2144 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2145 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2149 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2151 vid_running_time_end =
2152 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2155 /* If timestamp and duration are valid */
2156 if (valid_text_time) {
2158 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2160 text_running_time_end =
2161 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2165 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2166 GST_TIME_ARGS (text_running_time),
2167 GST_TIME_ARGS (text_running_time_end));
2168 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2169 GST_TIME_ARGS (vid_running_time),
2170 GST_TIME_ARGS (vid_running_time_end));
2172 /* Text too old or in the future */
2173 if (valid_text_time && text_running_time_end <= vid_running_time) {
2174 /* text buffer too old, get rid of it and do nothing */
2175 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2177 gst_base_text_overlay_pop_text (overlay);
2178 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2179 goto wait_for_text_buf;
2180 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2181 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2182 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2183 /* Push the video frame */
2184 ret = gst_pad_push (overlay->srcpad, buffer);
2190 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2191 in_text = (gchar *) map.data;
2194 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2195 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2196 * here on purpose, this is something that needs fixing upstream */
2197 if (!g_utf8_validate (in_text, in_size, NULL)) {
2198 const gchar *end = NULL;
2200 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2201 in_text = g_strndup (in_text, in_size);
2202 while (!g_utf8_validate (in_text, in_size, &end) && end)
2203 *((gchar *) end) = '*';
2206 /* Get the string */
2207 if (overlay->have_pango_markup) {
2208 text = g_strndup (in_text, in_size);
2210 text = g_markup_escape_text (in_text, in_size);
2213 if (text != NULL && *text != '\0') {
2214 gint text_len = strlen (text);
2216 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2217 text[text_len - 1] == '\r')) {
2220 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2221 gst_base_text_overlay_render_text (overlay, text, text_len);
2223 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2224 gst_base_text_overlay_render_text (overlay, " ", 1);
2226 if (in_text != (gchar *) map.data)
2229 gst_buffer_unmap (overlay->text_buffer, &map);
2231 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2232 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2234 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2235 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2240 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2241 gst_base_text_overlay_pop_text (overlay);
2242 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2245 gboolean wait_for_text_buf = TRUE;
2247 if (overlay->text_eos)
2248 wait_for_text_buf = FALSE;
2250 if (!overlay->wait_text)
2251 wait_for_text_buf = FALSE;
2253 /* Text pad linked, but no text buffer available - what now? */
2254 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2255 GstClockTime text_start_running_time, text_position_running_time;
2256 GstClockTime vid_running_time;
2259 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2260 GST_BUFFER_TIMESTAMP (buffer));
2261 text_start_running_time =
2262 gst_segment_to_running_time (&overlay->text_segment,
2263 GST_FORMAT_TIME, overlay->text_segment.start);
2264 text_position_running_time =
2265 gst_segment_to_running_time (&overlay->text_segment,
2266 GST_FORMAT_TIME, overlay->text_segment.position);
2268 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2269 vid_running_time < text_start_running_time) ||
2270 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2271 vid_running_time < text_position_running_time)) {
2272 wait_for_text_buf = FALSE;
2276 if (wait_for_text_buf) {
2277 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2278 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2279 GST_DEBUG_OBJECT (overlay, "resuming");
2280 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2281 goto wait_for_text_buf;
2283 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2284 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2285 ret = gst_pad_push (overlay->srcpad, buffer);
2292 /* Update position */
2293 overlay->segment.position = clip_start;
2299 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2300 gst_buffer_unref (buffer);
2306 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2307 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2308 gst_buffer_unref (buffer);
2309 return GST_FLOW_FLUSHING;
2313 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2314 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2315 gst_buffer_unref (buffer);
2316 return GST_FLOW_EOS;
2320 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2321 gst_buffer_unref (buffer);
2326 static GstStateChangeReturn
2327 gst_base_text_overlay_change_state (GstElement * element,
2328 GstStateChange transition)
2330 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2331 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2333 switch (transition) {
2334 case GST_STATE_CHANGE_PAUSED_TO_READY:
2335 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2336 overlay->text_flushing = TRUE;
2337 overlay->video_flushing = TRUE;
2338 /* pop_text will broadcast on the GCond and thus also make the video
2339 * chain exit if it's waiting for a text buffer */
2340 gst_base_text_overlay_pop_text (overlay);
2341 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2347 ret = parent_class->change_state (element, transition);
2348 if (ret == GST_STATE_CHANGE_FAILURE)
2351 switch (transition) {
2352 case GST_STATE_CHANGE_READY_TO_PAUSED:
2353 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2354 overlay->text_flushing = FALSE;
2355 overlay->video_flushing = FALSE;
2356 overlay->video_eos = FALSE;
2357 overlay->text_eos = FALSE;
2358 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2359 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2360 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2370 plugin_init (GstPlugin * plugin)
2372 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2373 GST_TYPE_TEXT_OVERLAY) ||
2374 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2375 GST_TYPE_TIME_OVERLAY) ||
2376 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2377 GST_TYPE_CLOCK_OVERLAY) ||
2378 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2379 GST_TYPE_TEXT_RENDER)) {
2383 /*texttestsrc_plugin_init(module, plugin); */
2385 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2390 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2391 pango, "Pango-based text rendering and overlay", plugin_init,
2392 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)