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,
335 GstObject * parent, GstPad * peer);
336 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
338 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
339 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
342 static void gst_base_text_overlay_finalize (GObject * object);
343 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
344 const GValue * value, GParamSpec * pspec);
345 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
346 GValue * value, GParamSpec * pspec);
348 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
349 PangoFontDescription * desc);
352 gst_base_text_overlay_get_type (void)
354 static GType type = 0;
356 if (g_once_init_enter ((gsize *) & type)) {
357 static const GTypeInfo info = {
358 sizeof (GstBaseTextOverlayClass),
359 (GBaseInitFunc) gst_base_text_overlay_base_init,
361 (GClassInitFunc) gst_base_text_overlay_class_init,
364 sizeof (GstBaseTextOverlay),
366 (GInstanceInitFunc) gst_base_text_overlay_init,
369 g_once_init_leave ((gsize *) & type,
370 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
378 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
379 GstBuffer * video_frame)
381 return g_strdup (overlay->default_text);
385 gst_base_text_overlay_base_init (gpointer g_class)
387 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
388 PangoFontMap *fontmap;
390 /* Only lock for the subclasses here, the base class
391 * doesn't have this mutex yet and it's not necessary
393 if (klass->pango_lock)
394 g_mutex_lock (klass->pango_lock);
395 fontmap = pango_cairo_font_map_get_default ();
396 klass->pango_context =
397 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
398 if (klass->pango_lock)
399 g_mutex_unlock (klass->pango_lock);
403 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
405 GObjectClass *gobject_class;
406 GstElementClass *gstelement_class;
408 gobject_class = (GObjectClass *) klass;
409 gstelement_class = (GstElementClass *) klass;
411 parent_class = g_type_class_peek_parent (klass);
413 gobject_class->finalize = gst_base_text_overlay_finalize;
414 gobject_class->set_property = gst_base_text_overlay_set_property;
415 gobject_class->get_property = gst_base_text_overlay_get_property;
417 gst_element_class_add_pad_template (gstelement_class,
418 gst_static_pad_template_get (&src_template_factory));
419 gst_element_class_add_pad_template (gstelement_class,
420 gst_static_pad_template_get (&video_sink_template_factory));
422 gstelement_class->change_state =
423 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
425 klass->pango_lock = g_slice_new (GMutex);
426 g_mutex_init (klass->pango_lock);
428 klass->get_text = gst_base_text_overlay_get_text;
430 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
431 g_param_spec_string ("text", "text",
432 "Text to be display.", DEFAULT_PROP_TEXT,
433 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
434 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
435 g_param_spec_boolean ("shaded-background", "shaded background",
436 "Whether to shade the background under the text area",
437 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
438 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
439 g_param_spec_enum ("valignment", "vertical alignment",
440 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
441 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
442 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
443 g_param_spec_enum ("halignment", "horizontal alignment",
444 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
445 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
446 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
447 g_param_spec_int ("xpad", "horizontal paddding",
448 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
449 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
450 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
451 g_param_spec_int ("ypad", "vertical padding",
452 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
453 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
454 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
455 g_param_spec_int ("deltax", "X position modifier",
456 "Shift X position to the left or to the right. Unit is pixels.",
457 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
458 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
459 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
460 g_param_spec_int ("deltay", "Y position modifier",
461 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
462 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
464 * GstBaseTextOverlay:xpos
466 * Horizontal position of the rendered text when using positioned alignment.
470 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
471 g_param_spec_double ("xpos", "horizontal position",
472 "Horizontal position when using position alignment", 0, 1.0,
474 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
476 * GstBaseTextOverlay:ypos
478 * Vertical position of the rendered text when using positioned alignment.
482 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
483 g_param_spec_double ("ypos", "vertical position",
484 "Vertical position when using position alignment", 0, 1.0,
486 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
487 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
488 g_param_spec_enum ("wrap-mode", "wrap mode",
489 "Whether to wrap the text and if so how.",
490 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
491 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
492 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
493 g_param_spec_string ("font-desc", "font description",
494 "Pango font description of font to be used for rendering. "
495 "See documentation of pango_font_description_from_string "
496 "for syntax.", DEFAULT_PROP_FONT_DESC,
497 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
499 * GstBaseTextOverlay:color
501 * Color of the rendered text.
505 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
506 g_param_spec_uint ("color", "Color",
507 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
509 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
511 * GstTextOverlay:outline-color
513 * Color of the outline of the rendered text.
517 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
518 g_param_spec_uint ("outline-color", "Text Outline Color",
519 "Color to use for outline the text (big-endian ARGB).", 0,
520 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
521 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
524 * GstBaseTextOverlay:line-alignment
526 * Alignment of text lines relative to each other (for multi-line text)
530 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
531 g_param_spec_enum ("line-alignment", "line alignment",
532 "Alignment of text lines relative to each other.",
533 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
534 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
536 * GstBaseTextOverlay:silent
538 * If set, no text is rendered. Useful to switch off text rendering
539 * temporarily without removing the textoverlay element from the pipeline.
543 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
544 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
545 g_param_spec_boolean ("silent", "silent",
546 "Whether to render the text string",
548 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
550 * GstBaseTextOverlay:wait-text
552 * If set, the video will block until a subtitle is received on the text pad.
553 * If video and subtitles are sent in sync, like from the same demuxer, this
554 * property should be set.
558 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
559 g_param_spec_boolean ("wait-text", "Wait Text",
560 "Whether to wait for subtitles",
561 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
563 g_object_class_install_property (G_OBJECT_CLASS (klass),
564 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
565 "Automatically adjust font size to screen-size.",
566 DEFAULT_PROP_AUTO_ADJUST_SIZE,
567 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
569 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
570 g_param_spec_boolean ("vertical-render", "vertical render",
571 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
572 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
576 gst_base_text_overlay_finalize (GObject * object)
578 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
580 g_free (overlay->default_text);
582 if (overlay->composition) {
583 gst_video_overlay_composition_unref (overlay->composition);
584 overlay->composition = NULL;
587 if (overlay->text_image) {
588 gst_buffer_unref (overlay->text_image);
589 overlay->text_image = NULL;
592 if (overlay->layout) {
593 g_object_unref (overlay->layout);
594 overlay->layout = NULL;
597 if (overlay->text_buffer) {
598 gst_buffer_unref (overlay->text_buffer);
599 overlay->text_buffer = NULL;
602 g_mutex_clear (&overlay->lock);
603 g_cond_clear (&overlay->cond);
605 G_OBJECT_CLASS (parent_class)->finalize (object);
609 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
610 GstBaseTextOverlayClass * klass)
612 GstPadTemplate *template;
613 PangoFontDescription *desc;
616 template = gst_static_pad_template_get (&video_sink_template_factory);
617 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
618 gst_object_unref (template);
619 gst_pad_set_event_function (overlay->video_sinkpad,
620 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
621 gst_pad_set_chain_function (overlay->video_sinkpad,
622 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
623 gst_pad_set_query_function (overlay->video_sinkpad,
624 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
625 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
628 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
632 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
634 gst_pad_set_event_function (overlay->text_sinkpad,
635 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
636 gst_pad_set_chain_function (overlay->text_sinkpad,
637 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
638 gst_pad_set_link_function (overlay->text_sinkpad,
639 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
640 gst_pad_set_unlink_function (overlay->text_sinkpad,
641 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
642 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
646 template = gst_static_pad_template_get (&src_template_factory);
647 overlay->srcpad = gst_pad_new_from_template (template, "src");
648 gst_object_unref (template);
649 gst_pad_set_event_function (overlay->srcpad,
650 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
651 gst_pad_set_query_function (overlay->srcpad,
652 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
653 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
655 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
656 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
658 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
659 (overlay)->pango_context);
661 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
662 (overlay)->pango_context);
663 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
665 overlay->color = DEFAULT_PROP_COLOR;
666 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
667 overlay->halign = DEFAULT_PROP_HALIGNMENT;
668 overlay->valign = DEFAULT_PROP_VALIGNMENT;
669 overlay->xpad = DEFAULT_PROP_XPAD;
670 overlay->ypad = DEFAULT_PROP_YPAD;
671 overlay->deltax = DEFAULT_PROP_DELTAX;
672 overlay->deltay = DEFAULT_PROP_DELTAY;
673 overlay->xpos = DEFAULT_PROP_XPOS;
674 overlay->ypos = DEFAULT_PROP_YPOS;
676 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
678 overlay->want_shading = DEFAULT_PROP_SHADING;
679 overlay->shading_value = DEFAULT_SHADING_VALUE;
680 overlay->silent = DEFAULT_PROP_SILENT;
681 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
682 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
684 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
685 overlay->need_render = TRUE;
686 overlay->text_image = NULL;
687 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
688 gst_base_text_overlay_update_render_mode (overlay);
690 overlay->text_buffer = NULL;
691 overlay->text_linked = FALSE;
692 g_mutex_init (&overlay->lock);
693 g_cond_init (&overlay->cond);
694 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
695 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
699 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
701 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
702 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
703 pango_layout_set_width (overlay->layout, -1);
707 if (overlay->auto_adjust_size) {
708 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
709 if (overlay->use_vertical_render) {
710 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
714 (overlay->use_vertical_render ? overlay->height : overlay->width) *
718 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
719 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
720 pango_layout_set_width (overlay->layout, width);
721 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
726 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
728 PangoMatrix matrix = PANGO_MATRIX_INIT;
729 PangoContext *context = pango_layout_get_context (overlay->layout);
731 if (overlay->use_vertical_render) {
732 pango_matrix_rotate (&matrix, -90);
733 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
734 pango_context_set_matrix (context, &matrix);
735 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
737 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
738 pango_context_set_matrix (context, &matrix);
739 pango_layout_set_alignment (overlay->layout,
740 (PangoAlignment) overlay->line_align);
745 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
747 GstStructure *structure;
750 structure = gst_caps_get_structure (caps, 0);
751 format = gst_structure_get_string (structure, "format");
752 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
757 /* FIXME: upstream nego (e.g. when the video window is resized) */
759 /* only negotiate/query video overlay composition support for now */
761 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
765 gboolean attach = FALSE;
767 GST_DEBUG_OBJECT (overlay, "performing negotiation");
769 target = gst_pad_get_current_caps (overlay->srcpad);
771 if (!target || gst_caps_is_empty (target))
774 /* find supported meta */
775 query = gst_query_new_allocation (target, TRUE);
777 if (!gst_pad_peer_query (overlay->srcpad, query)) {
778 /* no problem, we use the query defaults */
779 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
782 if (gst_query_find_allocation_meta (query,
783 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
786 overlay->attach_compo_to_buffer = attach;
788 gst_query_unref (query);
789 gst_caps_unref (target);
796 gst_caps_unref (target);
802 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
805 gboolean ret = FALSE;
807 if (!gst_video_info_from_caps (&info, caps))
810 overlay->info = info;
811 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
812 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
813 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
815 ret = gst_pad_set_caps (overlay->srcpad, caps);
818 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
819 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
820 gst_base_text_overlay_negotiate (overlay);
821 gst_base_text_overlay_update_wrap_mode (overlay);
822 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
823 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
831 GST_DEBUG_OBJECT (overlay, "could not parse caps");
837 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
838 const GValue * value, GParamSpec * pspec)
840 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
842 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
845 g_free (overlay->default_text);
846 overlay->default_text = g_value_dup_string (value);
847 overlay->need_render = TRUE;
850 overlay->want_shading = g_value_get_boolean (value);
853 overlay->xpad = g_value_get_int (value);
856 overlay->ypad = g_value_get_int (value);
859 overlay->deltax = g_value_get_int (value);
862 overlay->deltay = g_value_get_int (value);
865 overlay->xpos = g_value_get_double (value);
868 overlay->ypos = g_value_get_double (value);
870 case PROP_VALIGNMENT:
871 overlay->valign = g_value_get_enum (value);
873 case PROP_HALIGNMENT:
874 overlay->halign = g_value_get_enum (value);
877 overlay->wrap_mode = g_value_get_enum (value);
878 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
879 gst_base_text_overlay_update_wrap_mode (overlay);
880 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
884 PangoFontDescription *desc;
885 const gchar *fontdesc_str;
887 fontdesc_str = g_value_get_string (value);
888 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
889 desc = pango_font_description_from_string (fontdesc_str);
891 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
892 pango_layout_set_font_description (overlay->layout, desc);
893 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
894 pango_font_description_free (desc);
896 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
899 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
903 overlay->color = g_value_get_uint (value);
905 case PROP_OUTLINE_COLOR:
906 overlay->outline_color = g_value_get_uint (value);
909 overlay->silent = g_value_get_boolean (value);
911 case PROP_LINE_ALIGNMENT:
912 overlay->line_align = g_value_get_enum (value);
913 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
914 pango_layout_set_alignment (overlay->layout,
915 (PangoAlignment) overlay->line_align);
916 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
919 overlay->wait_text = g_value_get_boolean (value);
921 case PROP_AUTO_ADJUST_SIZE:
922 overlay->auto_adjust_size = g_value_get_boolean (value);
923 overlay->need_render = TRUE;
925 case PROP_VERTICAL_RENDER:
926 overlay->use_vertical_render = g_value_get_boolean (value);
927 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
928 gst_base_text_overlay_update_render_mode (overlay);
929 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
930 overlay->need_render = TRUE;
933 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
937 overlay->need_render = TRUE;
938 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
942 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
943 GValue * value, GParamSpec * pspec)
945 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
947 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
950 g_value_set_string (value, overlay->default_text);
953 g_value_set_boolean (value, overlay->want_shading);
956 g_value_set_int (value, overlay->xpad);
959 g_value_set_int (value, overlay->ypad);
962 g_value_set_int (value, overlay->deltax);
965 g_value_set_int (value, overlay->deltay);
968 g_value_set_double (value, overlay->xpos);
971 g_value_set_double (value, overlay->ypos);
973 case PROP_VALIGNMENT:
974 g_value_set_enum (value, overlay->valign);
976 case PROP_HALIGNMENT:
977 g_value_set_enum (value, overlay->halign);
980 g_value_set_enum (value, overlay->wrap_mode);
983 g_value_set_boolean (value, overlay->silent);
985 case PROP_LINE_ALIGNMENT:
986 g_value_set_enum (value, overlay->line_align);
989 g_value_set_boolean (value, overlay->wait_text);
991 case PROP_AUTO_ADJUST_SIZE:
992 g_value_set_boolean (value, overlay->auto_adjust_size);
994 case PROP_VERTICAL_RENDER:
995 g_value_set_boolean (value, overlay->use_vertical_render);
998 g_value_set_uint (value, overlay->color);
1000 case PROP_OUTLINE_COLOR:
1001 g_value_set_uint (value, overlay->outline_color);
1004 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1008 overlay->need_render = TRUE;
1009 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1013 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1016 gboolean ret = FALSE;
1017 GstBaseTextOverlay *overlay;
1019 overlay = GST_BASE_TEXT_OVERLAY (parent);
1021 switch (GST_QUERY_TYPE (query)) {
1022 case GST_QUERY_CAPS:
1024 GstCaps *filter, *caps;
1026 gst_query_parse_caps (query, &filter);
1027 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1028 gst_query_set_caps_result (query, caps);
1029 gst_caps_unref (caps);
1034 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1042 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1045 gboolean ret = FALSE;
1046 GstBaseTextOverlay *overlay = NULL;
1048 overlay = GST_BASE_TEXT_OVERLAY (parent);
1050 switch (GST_EVENT_TYPE (event)) {
1051 case GST_EVENT_SEEK:{
1054 /* We don't handle seek if we have not text pad */
1055 if (!overlay->text_linked) {
1056 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1057 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1061 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1063 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1065 /* Flush downstream, only for flushing seek */
1066 if (flags & GST_SEEK_FLAG_FLUSH)
1067 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1069 /* Mark ourself as flushing, unblock chains */
1070 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1071 overlay->video_flushing = TRUE;
1072 overlay->text_flushing = TRUE;
1073 gst_base_text_overlay_pop_text (overlay);
1074 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1076 /* Seek on each sink pad */
1077 gst_event_ref (event);
1078 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1080 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1082 gst_event_unref (event);
1087 if (overlay->text_linked) {
1088 gst_event_ref (event);
1089 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1090 gst_pad_push_event (overlay->text_sinkpad, event);
1092 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1103 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1109 if (G_UNLIKELY (!overlay))
1110 return gst_pad_get_pad_template_caps (pad);
1112 if (pad == overlay->srcpad)
1113 otherpad = overlay->video_sinkpad;
1115 otherpad = overlay->srcpad;
1117 /* we can do what the peer can */
1118 caps = gst_pad_peer_query_caps (otherpad, filter);
1120 GstCaps *temp, *templ;
1122 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1124 /* filtered against our padtemplate */
1125 templ = gst_pad_get_pad_template_caps (otherpad);
1126 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1127 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1128 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1129 gst_caps_unref (caps);
1130 gst_caps_unref (templ);
1131 /* this is what we can do */
1134 /* no peer, our padtemplate is enough then */
1135 caps = gst_pad_get_pad_template_caps (pad);
1137 GstCaps *intersection;
1140 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1141 gst_caps_unref (caps);
1142 caps = intersection;
1146 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1152 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1153 PangoFontDescription * desc)
1155 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1156 overlay->shadow_offset = (double) (font_size) / 13.0;
1157 overlay->outline_offset = (double) (font_size) / 15.0;
1158 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1159 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1163 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1164 gint * xpos, gint * ypos)
1167 GstBaseTextOverlayVAlign valign;
1168 GstBaseTextOverlayHAlign halign;
1170 width = overlay->image_width;
1171 height = overlay->image_height;
1173 if (overlay->use_vertical_render)
1174 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1176 halign = overlay->halign;
1179 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1180 *xpos = overlay->xpad;
1182 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1183 *xpos = (overlay->width - width) / 2;
1185 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1186 *xpos = overlay->width - width - overlay->xpad;
1188 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1189 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1190 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1197 *xpos += overlay->deltax;
1199 if (overlay->use_vertical_render)
1200 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1202 valign = overlay->valign;
1205 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1206 *ypos = overlay->height - height - overlay->ypad;
1208 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1209 *ypos = overlay->height - (height + overlay->ypad);
1211 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1212 *ypos = overlay->ypad;
1214 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1215 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1216 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1218 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1219 *ypos = (overlay->height - height) / 2;
1222 *ypos = overlay->ypad;
1225 *ypos += overlay->deltay;
1229 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1232 GstVideoOverlayRectangle *rectangle;
1234 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1236 if (overlay->text_image) {
1237 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1238 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1239 overlay->image_width, overlay->image_height);
1240 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1241 xpos, ypos, overlay->image_width, overlay->image_height,
1242 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1244 if (overlay->composition)
1245 gst_video_overlay_composition_unref (overlay->composition);
1246 overlay->composition = gst_video_overlay_composition_new (rectangle);
1247 gst_video_overlay_rectangle_unref (rectangle);
1249 } else if (overlay->composition) {
1250 gst_video_overlay_composition_unref (overlay->composition);
1251 overlay->composition = NULL;
1256 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1258 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1266 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1267 const gchar * string, gint textlen)
1270 cairo_surface_t *surface;
1271 PangoRectangle ink_rect, logical_rect;
1272 cairo_matrix_t cairo_matrix;
1274 double scalef = 1.0;
1279 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1281 if (overlay->auto_adjust_size) {
1282 /* 640 pixel is default */
1283 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1285 pango_layout_set_width (overlay->layout, -1);
1286 /* set text on pango layout */
1287 pango_layout_set_markup (overlay->layout, string, textlen);
1289 /* get subtitle image size */
1290 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1292 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1294 if (width + overlay->deltax >
1295 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1297 * subtitle image width is larger then overlay width
1298 * so rearrange overlay wrap mode.
1300 gst_base_text_overlay_update_wrap_mode (overlay);
1301 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1302 width = overlay->width;
1306 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1307 if (height > overlay->height) {
1308 height = overlay->height;
1310 if (overlay->use_vertical_render) {
1311 PangoRectangle rect;
1312 PangoContext *context;
1313 PangoMatrix matrix = PANGO_MATRIX_INIT;
1316 context = pango_layout_get_context (overlay->layout);
1318 pango_matrix_rotate (&matrix, -90);
1320 rect.x = rect.y = 0;
1322 rect.height = height;
1323 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1324 matrix.x0 = -rect.x;
1325 matrix.y0 = -rect.y;
1327 pango_context_set_matrix (context, &matrix);
1329 cairo_matrix.xx = matrix.xx;
1330 cairo_matrix.yx = matrix.yx;
1331 cairo_matrix.xy = matrix.xy;
1332 cairo_matrix.yy = matrix.yy;
1333 cairo_matrix.x0 = matrix.x0;
1334 cairo_matrix.y0 = matrix.y0;
1335 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1341 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1344 /* reallocate overlay buffer */
1345 buffer = gst_buffer_new_and_alloc (4 * width * height);
1346 gst_buffer_replace (&overlay->text_image, buffer);
1347 gst_buffer_unref (buffer);
1349 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1350 surface = cairo_image_surface_create_for_data (map.data,
1351 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1352 cr = cairo_create (surface);
1355 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1358 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1360 if (overlay->want_shading)
1361 cairo_paint_with_alpha (cr, overlay->shading_value);
1363 /* apply transformations */
1364 cairo_set_matrix (cr, &cairo_matrix);
1366 /* FIXME: We use show_layout everywhere except for the surface
1367 * because it's really faster and internally does all kinds of
1368 * caching. Unfortunately we have to paint to a cairo path for
1369 * the outline and this is slow. Once Pango supports user fonts
1370 * we should use them, see
1371 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1373 * Idea would the be, to create a cairo user font that
1374 * does shadow, outline, text painting in the
1375 * render_glyph function.
1378 /* draw shadow text */
1380 PangoAttrList *origin_attr, *filtered_attr;
1383 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1385 pango_attr_list_filter (pango_attr_list_copy (origin_attr),
1386 gst_text_overlay_filter_foreground_attr, NULL);
1389 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1390 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1391 pango_layout_set_attributes (overlay->layout, filtered_attr);
1392 pango_cairo_show_layout (cr, overlay->layout);
1393 pango_layout_set_attributes (overlay->layout, origin_attr);
1394 pango_attr_list_unref (filtered_attr);
1395 pango_attr_list_unref (origin_attr);
1399 a = (overlay->outline_color >> 24) & 0xff;
1400 r = (overlay->outline_color >> 16) & 0xff;
1401 g = (overlay->outline_color >> 8) & 0xff;
1402 b = (overlay->outline_color >> 0) & 0xff;
1404 /* draw outline text */
1406 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1407 cairo_set_line_width (cr, overlay->outline_offset);
1408 pango_cairo_layout_path (cr, overlay->layout);
1412 a = (overlay->color >> 24) & 0xff;
1413 r = (overlay->color >> 16) & 0xff;
1414 g = (overlay->color >> 8) & 0xff;
1415 b = (overlay->color >> 0) & 0xff;
1419 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1420 pango_cairo_show_layout (cr, overlay->layout);
1424 cairo_surface_destroy (surface);
1425 gst_buffer_unmap (buffer, &map);
1426 overlay->image_width = width;
1427 overlay->image_height = height;
1428 overlay->baseline_y = ink_rect.y;
1429 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1431 gst_base_text_overlay_set_composition (overlay);
1438 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1439 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1441 gint i, j, dest_stride;
1444 dest_stride = dest->info.stride[0];
1445 dest_ptr = dest->data[0];
1447 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1448 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1450 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1451 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1453 for (i = y0; i < y1; ++i) {
1454 for (j = x0; j < x1; ++j) {
1455 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1457 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1463 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1464 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1467 guint dest_stride, pixel_stride;
1470 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1471 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1472 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1474 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1475 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1477 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1478 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1481 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1483 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1486 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1488 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1490 for (i = y0; i < y1; i++) {
1491 for (j = x0; j < x1; j++) {
1495 y_pos = (i * dest_stride) + j * pixel_stride;
1496 y = dest_ptr[y_pos] + overlay->shading_value;
1498 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1503 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1504 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1505 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1507 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1508 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1513 dest_ptr = dest->data[0];
1515 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1516 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1518 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1519 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1521 for (i = y0; i < y1; i++) {
1522 for (j = x0; j < x1; j++) {
1525 y_pos = (i * 4 * overlay->width) + j * 4;
1526 for (k = 0; k < 4; k++) {
1527 y = dest_ptr[y_pos + k] + overlay->shading_value;
1528 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1534 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1535 static inline void \
1536 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1537 gint x0, gint x1, gint y0, gint y1) \
1542 dest_ptr = dest->data[0];\
1544 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1545 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1547 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1548 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1550 for (i = y0; i < y1; i++) {\
1551 for (j = x0; j < x1; j++) {\
1553 y_pos = (i * 4 * overlay->width) + j * 4;\
1554 for (k = OFFSET; k < 3+OFFSET; k++) {\
1555 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1556 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1561 ARGB_SHADE_FUNCTION (ARGB, 1);
1562 ARGB_SHADE_FUNCTION (ABGR, 1);
1563 ARGB_SHADE_FUNCTION (RGBA, 0);
1564 ARGB_SHADE_FUNCTION (BGRA, 0);
1567 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1568 const gchar * text, gint textlen)
1572 if (!overlay->need_render) {
1573 GST_DEBUG ("Using previously rendered text.");
1577 /* -1 is the whole string */
1578 if (text != NULL && textlen < 0) {
1579 textlen = strlen (text);
1583 string = g_strndup (text, textlen);
1584 } else { /* empty string */
1585 string = g_strdup (" ");
1587 g_strdelimit (string, "\r\t", ' ');
1588 textlen = strlen (string);
1590 /* FIXME: should we check for UTF-8 here? */
1592 GST_DEBUG ("Rendering '%s'", string);
1593 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1597 overlay->need_render = FALSE;
1600 static GstFlowReturn
1601 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1602 GstBuffer * video_frame)
1605 GstVideoFrame frame;
1607 if (overlay->composition == NULL)
1610 if (gst_pad_check_reconfigure (overlay->srcpad))
1611 gst_base_text_overlay_negotiate (overlay);
1613 video_frame = gst_buffer_make_writable (video_frame);
1615 if (overlay->attach_compo_to_buffer) {
1616 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1617 gst_buffer_add_video_overlay_composition_meta (video_frame,
1618 overlay->composition);
1619 /* FIXME: emulate shaded background box if want_shading=true */
1623 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1627 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1629 /* shaded background box */
1630 if (overlay->want_shading) {
1631 switch (overlay->format) {
1632 case GST_VIDEO_FORMAT_I420:
1633 case GST_VIDEO_FORMAT_NV12:
1634 case GST_VIDEO_FORMAT_NV21:
1635 gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1636 xpos, xpos + overlay->image_width,
1637 ypos, ypos + overlay->image_height);
1639 case GST_VIDEO_FORMAT_AYUV:
1640 case GST_VIDEO_FORMAT_UYVY:
1641 gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1642 xpos, xpos + overlay->image_width,
1643 ypos, ypos + overlay->image_height);
1645 case GST_VIDEO_FORMAT_xRGB:
1646 gst_base_text_overlay_shade_xRGB (overlay, &frame,
1647 xpos, xpos + overlay->image_width,
1648 ypos, ypos + overlay->image_height);
1650 case GST_VIDEO_FORMAT_xBGR:
1651 gst_base_text_overlay_shade_xBGR (overlay, &frame,
1652 xpos, xpos + overlay->image_width,
1653 ypos, ypos + overlay->image_height);
1655 case GST_VIDEO_FORMAT_BGRx:
1656 gst_base_text_overlay_shade_BGRx (overlay, &frame,
1657 xpos, xpos + overlay->image_width,
1658 ypos, ypos + overlay->image_height);
1660 case GST_VIDEO_FORMAT_RGBx:
1661 gst_base_text_overlay_shade_RGBx (overlay, &frame,
1662 xpos, xpos + overlay->image_width,
1663 ypos, ypos + overlay->image_height);
1665 case GST_VIDEO_FORMAT_ARGB:
1666 gst_base_text_overlay_shade_ARGB (overlay, &frame,
1667 xpos, xpos + overlay->image_width,
1668 ypos, ypos + overlay->image_height);
1670 case GST_VIDEO_FORMAT_ABGR:
1671 gst_base_text_overlay_shade_ABGR (overlay, &frame,
1672 xpos, xpos + overlay->image_width,
1673 ypos, ypos + overlay->image_height);
1675 case GST_VIDEO_FORMAT_RGBA:
1676 gst_base_text_overlay_shade_RGBA (overlay, &frame,
1677 xpos, xpos + overlay->image_width,
1678 ypos, ypos + overlay->image_height);
1680 case GST_VIDEO_FORMAT_BGRA:
1681 gst_base_text_overlay_shade_BGRA (overlay, &frame,
1682 xpos, xpos + overlay->image_width,
1683 ypos, ypos + overlay->image_height);
1686 g_assert_not_reached ();
1690 gst_video_overlay_composition_blend (overlay->composition, &frame);
1692 gst_video_frame_unmap (&frame);
1696 return gst_pad_push (overlay->srcpad, video_frame);
1701 gst_buffer_unref (video_frame);
1702 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1707 static GstPadLinkReturn
1708 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
1711 GstBaseTextOverlay *overlay;
1713 overlay = GST_BASE_TEXT_OVERLAY (parent);
1714 if (G_UNLIKELY (!overlay))
1715 return GST_PAD_LINK_REFUSED;
1717 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1719 overlay->text_linked = TRUE;
1721 return GST_PAD_LINK_OK;
1725 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
1727 GstBaseTextOverlay *overlay;
1729 /* don't use gst_pad_get_parent() here, will deadlock */
1730 overlay = GST_BASE_TEXT_OVERLAY (parent);
1732 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1734 overlay->text_linked = FALSE;
1736 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1740 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1743 gboolean ret = FALSE;
1744 GstBaseTextOverlay *overlay = NULL;
1746 overlay = GST_BASE_TEXT_OVERLAY (parent);
1748 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1750 switch (GST_EVENT_TYPE (event)) {
1751 case GST_EVENT_CAPS:
1755 gst_event_parse_caps (event, &caps);
1756 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1757 gst_event_unref (event);
1760 case GST_EVENT_SEGMENT:
1762 const GstSegment *segment;
1764 overlay->text_eos = FALSE;
1766 gst_event_parse_segment (event, &segment);
1768 if (segment->format == GST_FORMAT_TIME) {
1769 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1770 gst_segment_copy_into (segment, &overlay->text_segment);
1771 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1772 &overlay->text_segment);
1773 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1775 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1776 ("received non-TIME newsegment event on text input"));
1779 gst_event_unref (event);
1782 /* wake up the video chain, it might be waiting for a text buffer or
1783 * a text segment update */
1784 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1785 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1786 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1791 GstClockTime start, duration;
1793 gst_event_parse_gap (event, &start, &duration);
1794 if (GST_CLOCK_TIME_IS_VALID (duration))
1796 /* we do not expect another buffer until after gap,
1797 * so that is our position now */
1798 overlay->text_segment.position = start;
1800 /* wake up the video chain, it might be waiting for a text buffer or
1801 * a text segment update */
1802 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1803 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1804 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1807 case GST_EVENT_FLUSH_STOP:
1808 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1809 GST_INFO_OBJECT (overlay, "text flush stop");
1810 overlay->text_flushing = FALSE;
1811 overlay->text_eos = FALSE;
1812 gst_base_text_overlay_pop_text (overlay);
1813 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1814 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1815 gst_event_unref (event);
1818 case GST_EVENT_FLUSH_START:
1819 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1820 GST_INFO_OBJECT (overlay, "text flush start");
1821 overlay->text_flushing = TRUE;
1822 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1823 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1824 gst_event_unref (event);
1828 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1829 overlay->text_eos = TRUE;
1830 GST_INFO_OBJECT (overlay, "text EOS");
1831 /* wake up the video chain, it might be waiting for a text buffer or
1832 * a text segment update */
1833 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1834 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1835 gst_event_unref (event);
1839 ret = gst_pad_event_default (pad, parent, event);
1847 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1850 gboolean ret = FALSE;
1851 GstBaseTextOverlay *overlay = NULL;
1853 overlay = GST_BASE_TEXT_OVERLAY (parent);
1855 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1857 switch (GST_EVENT_TYPE (event)) {
1858 case GST_EVENT_CAPS:
1862 gst_event_parse_caps (event, &caps);
1863 ret = gst_base_text_overlay_setcaps (overlay, caps);
1864 gst_event_unref (event);
1867 case GST_EVENT_SEGMENT:
1869 const GstSegment *segment;
1871 GST_DEBUG_OBJECT (overlay, "received new segment");
1873 gst_event_parse_segment (event, &segment);
1875 if (segment->format == GST_FORMAT_TIME) {
1876 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1879 gst_segment_copy_into (segment, &overlay->segment);
1881 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1882 ("received non-TIME newsegment event on video input"));
1885 ret = gst_pad_event_default (pad, parent, event);
1889 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1890 GST_INFO_OBJECT (overlay, "video EOS");
1891 overlay->video_eos = TRUE;
1892 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1893 ret = gst_pad_event_default (pad, parent, event);
1895 case GST_EVENT_FLUSH_START:
1896 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1897 GST_INFO_OBJECT (overlay, "video flush start");
1898 overlay->video_flushing = TRUE;
1899 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1900 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1901 ret = gst_pad_event_default (pad, parent, event);
1903 case GST_EVENT_FLUSH_STOP:
1904 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1905 GST_INFO_OBJECT (overlay, "video flush stop");
1906 overlay->video_flushing = FALSE;
1907 overlay->video_eos = FALSE;
1908 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1909 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1910 ret = gst_pad_event_default (pad, parent, event);
1913 ret = gst_pad_event_default (pad, parent, event);
1921 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1924 gboolean ret = FALSE;
1925 GstBaseTextOverlay *overlay;
1927 overlay = GST_BASE_TEXT_OVERLAY (parent);
1929 switch (GST_QUERY_TYPE (query)) {
1930 case GST_QUERY_CAPS:
1932 GstCaps *filter, *caps;
1934 gst_query_parse_caps (query, &filter);
1935 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1936 gst_query_set_caps_result (query, caps);
1937 gst_caps_unref (caps);
1942 ret = gst_pad_query_default (pad, parent, query);
1949 /* Called with lock held */
1951 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1953 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1955 if (overlay->text_buffer) {
1956 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1957 overlay->text_buffer);
1958 gst_buffer_unref (overlay->text_buffer);
1959 overlay->text_buffer = NULL;
1962 /* Let the text task know we used that buffer */
1963 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1966 /* We receive text buffers here. If they are out of segment we just ignore them.
1967 If the buffer is in our segment we keep it internally except if another one
1968 is already waiting here, in that case we wait that it gets kicked out */
1969 static GstFlowReturn
1970 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1973 GstFlowReturn ret = GST_FLOW_OK;
1974 GstBaseTextOverlay *overlay = NULL;
1975 gboolean in_seg = FALSE;
1976 guint64 clip_start = 0, clip_stop = 0;
1978 overlay = GST_BASE_TEXT_OVERLAY (parent);
1980 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1982 if (overlay->text_flushing) {
1983 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1984 ret = GST_FLOW_FLUSHING;
1985 GST_LOG_OBJECT (overlay, "text flushing");
1989 if (overlay->text_eos) {
1990 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1992 GST_LOG_OBJECT (overlay, "text EOS");
1996 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1997 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1998 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1999 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2000 GST_BUFFER_DURATION (buffer)));
2002 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2005 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2006 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2008 stop = GST_CLOCK_TIME_NONE;
2010 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2011 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2017 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2018 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2019 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2020 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2022 /* Wait for the previous buffer to go away */
2023 while (overlay->text_buffer != NULL) {
2024 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2025 GST_DEBUG_PAD_NAME (pad));
2026 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2027 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2028 if (overlay->text_flushing) {
2029 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2030 ret = GST_FLOW_FLUSHING;
2035 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2036 overlay->text_segment.position = clip_start;
2038 overlay->text_buffer = buffer;
2039 /* That's a new text buffer we need to render */
2040 overlay->need_render = TRUE;
2042 /* in case the video chain is waiting for a text buffer, wake it up */
2043 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2046 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2053 static GstFlowReturn
2054 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2057 GstBaseTextOverlayClass *klass;
2058 GstBaseTextOverlay *overlay;
2059 GstFlowReturn ret = GST_FLOW_OK;
2060 gboolean in_seg = FALSE;
2061 guint64 start, stop, clip_start = 0, clip_stop = 0;
2064 overlay = GST_BASE_TEXT_OVERLAY (parent);
2065 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2067 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2068 goto missing_timestamp;
2070 /* ignore buffers that are outside of the current segment */
2071 start = GST_BUFFER_TIMESTAMP (buffer);
2073 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2074 stop = GST_CLOCK_TIME_NONE;
2076 stop = start + GST_BUFFER_DURATION (buffer);
2079 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2080 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2081 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2083 /* segment_clip() will adjust start unconditionally to segment_start if
2084 * no stop time is provided, so handle this ourselves */
2085 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2086 goto out_of_segment;
2088 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2089 &clip_start, &clip_stop);
2092 goto out_of_segment;
2094 /* if the buffer is only partially in the segment, fix up stamps */
2095 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2096 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2097 buffer = gst_buffer_make_writable (buffer);
2098 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2100 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2103 /* now, after we've done the clipping, fix up end time if there's no
2104 * duration (we only use those estimated values internally though, we
2105 * don't want to set bogus values on the buffer itself) */
2109 gint fps_num, fps_denom;
2111 /* FIXME, store this in setcaps */
2112 caps = gst_pad_get_current_caps (pad);
2113 s = gst_caps_get_structure (caps, 0);
2114 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2115 fps_num && fps_denom) {
2116 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2117 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2119 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2120 stop = start + 1; /* we need to assume some interval */
2122 gst_caps_unref (caps);
2125 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2129 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2131 if (overlay->video_flushing)
2134 if (overlay->video_eos)
2137 if (overlay->silent) {
2138 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2139 ret = gst_pad_push (overlay->srcpad, buffer);
2141 /* Update position */
2142 overlay->segment.position = clip_start;
2147 /* Text pad not linked, rendering internal text */
2148 if (!overlay->text_linked) {
2149 if (klass->get_text) {
2150 text = klass->get_text (overlay, buffer);
2152 text = g_strdup (overlay->default_text);
2155 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2156 "text: '%s'", GST_STR_NULL (text));
2158 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2160 if (text != NULL && *text != '\0') {
2161 /* Render and push */
2162 gst_base_text_overlay_render_text (overlay, text, -1);
2163 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2165 /* Invalid or empty string */
2166 ret = gst_pad_push (overlay->srcpad, buffer);
2169 /* Text pad linked, check if we have a text buffer queued */
2170 if (overlay->text_buffer) {
2171 gboolean pop_text = FALSE, valid_text_time = TRUE;
2172 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2173 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2174 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2175 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2176 GstClockTime vid_running_time, vid_running_time_end;
2178 /* if the text buffer isn't stamped right, pop it off the
2179 * queue and display it for the current video frame only */
2180 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2181 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2182 GST_WARNING_OBJECT (overlay,
2183 "Got text buffer with invalid timestamp or duration");
2185 valid_text_time = FALSE;
2187 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2188 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2192 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2194 vid_running_time_end =
2195 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2198 /* If timestamp and duration are valid */
2199 if (valid_text_time) {
2201 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2203 text_running_time_end =
2204 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2208 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2209 GST_TIME_ARGS (text_running_time),
2210 GST_TIME_ARGS (text_running_time_end));
2211 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2212 GST_TIME_ARGS (vid_running_time),
2213 GST_TIME_ARGS (vid_running_time_end));
2215 /* Text too old or in the future */
2216 if (valid_text_time && text_running_time_end <= vid_running_time) {
2217 /* text buffer too old, get rid of it and do nothing */
2218 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2220 gst_base_text_overlay_pop_text (overlay);
2221 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2222 goto wait_for_text_buf;
2223 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2224 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2225 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2226 /* Push the video frame */
2227 ret = gst_pad_push (overlay->srcpad, buffer);
2233 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2234 in_text = (gchar *) map.data;
2238 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2239 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2240 * here on purpose, this is something that needs fixing upstream */
2241 if (!g_utf8_validate (in_text, in_size, NULL)) {
2242 const gchar *end = NULL;
2244 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2245 in_text = g_strndup (in_text, in_size);
2246 while (!g_utf8_validate (in_text, in_size, &end) && end)
2247 *((gchar *) end) = '*';
2250 /* Get the string */
2251 if (overlay->have_pango_markup) {
2252 text = g_strndup (in_text, in_size);
2254 text = g_markup_escape_text (in_text, in_size);
2257 if (text != NULL && *text != '\0') {
2258 gint text_len = strlen (text);
2260 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2261 text[text_len - 1] == '\r')) {
2264 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2265 gst_base_text_overlay_render_text (overlay, text, text_len);
2267 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2268 gst_base_text_overlay_render_text (overlay, " ", 1);
2270 if (in_text != (gchar *) map.data)
2273 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2274 gst_base_text_overlay_render_text (overlay, " ", 1);
2277 gst_buffer_unmap (overlay->text_buffer, &map);
2279 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2280 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2282 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2283 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2288 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2289 gst_base_text_overlay_pop_text (overlay);
2290 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2293 gboolean wait_for_text_buf = TRUE;
2295 if (overlay->text_eos)
2296 wait_for_text_buf = FALSE;
2298 if (!overlay->wait_text)
2299 wait_for_text_buf = FALSE;
2301 /* Text pad linked, but no text buffer available - what now? */
2302 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2303 GstClockTime text_start_running_time, text_position_running_time;
2304 GstClockTime vid_running_time;
2307 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2308 GST_BUFFER_TIMESTAMP (buffer));
2309 text_start_running_time =
2310 gst_segment_to_running_time (&overlay->text_segment,
2311 GST_FORMAT_TIME, overlay->text_segment.start);
2312 text_position_running_time =
2313 gst_segment_to_running_time (&overlay->text_segment,
2314 GST_FORMAT_TIME, overlay->text_segment.position);
2316 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2317 vid_running_time < text_start_running_time) ||
2318 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2319 vid_running_time < text_position_running_time)) {
2320 wait_for_text_buf = FALSE;
2324 if (wait_for_text_buf) {
2325 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2326 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2327 GST_DEBUG_OBJECT (overlay, "resuming");
2328 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2329 goto wait_for_text_buf;
2331 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2332 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2333 ret = gst_pad_push (overlay->srcpad, buffer);
2340 /* Update position */
2341 overlay->segment.position = clip_start;
2347 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2348 gst_buffer_unref (buffer);
2354 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2355 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2356 gst_buffer_unref (buffer);
2357 return GST_FLOW_FLUSHING;
2361 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2362 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2363 gst_buffer_unref (buffer);
2364 return GST_FLOW_EOS;
2368 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2369 gst_buffer_unref (buffer);
2374 static GstStateChangeReturn
2375 gst_base_text_overlay_change_state (GstElement * element,
2376 GstStateChange transition)
2378 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2379 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2381 switch (transition) {
2382 case GST_STATE_CHANGE_PAUSED_TO_READY:
2383 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2384 overlay->text_flushing = TRUE;
2385 overlay->video_flushing = TRUE;
2386 /* pop_text will broadcast on the GCond and thus also make the video
2387 * chain exit if it's waiting for a text buffer */
2388 gst_base_text_overlay_pop_text (overlay);
2389 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2395 ret = parent_class->change_state (element, transition);
2396 if (ret == GST_STATE_CHANGE_FAILURE)
2399 switch (transition) {
2400 case GST_STATE_CHANGE_READY_TO_PAUSED:
2401 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2402 overlay->text_flushing = FALSE;
2403 overlay->video_flushing = FALSE;
2404 overlay->video_eos = FALSE;
2405 overlay->text_eos = FALSE;
2406 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2407 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2408 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2418 plugin_init (GstPlugin * plugin)
2420 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2421 GST_TYPE_TEXT_OVERLAY) ||
2422 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2423 GST_TYPE_TIME_OVERLAY) ||
2424 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2425 GST_TYPE_CLOCK_OVERLAY) ||
2426 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2427 GST_TYPE_TEXT_RENDER)) {
2431 /*texttestsrc_plugin_init(module, plugin); */
2433 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2438 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2439 pango, "Pango-based text rendering and overlay", plugin_init,
2440 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)