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., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
29 #include <gst/video/video.h>
30 #include <gst/video/gstvideometa.h>
32 #include "gstbasetextoverlay.h"
33 #include "gsttextoverlay.h"
34 #include "gsttimeoverlay.h"
35 #include "gstclockoverlay.h"
36 #include "gsttextrender.h"
41 * - use proper strides and offset for I420
42 * - if text is wider than the video picture, it does not get
43 * clipped properly during blitting (if wrapping is disabled)
46 GST_DEBUG_CATEGORY (pango_debug);
47 #define GST_CAT_DEFAULT pango_debug
49 #define DEFAULT_PROP_TEXT ""
50 #define DEFAULT_PROP_SHADING FALSE
51 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
52 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
53 #define DEFAULT_PROP_XPAD 25
54 #define DEFAULT_PROP_YPAD 25
55 #define DEFAULT_PROP_DELTAX 0
56 #define DEFAULT_PROP_DELTAY 0
57 #define DEFAULT_PROP_XPOS 0.5
58 #define DEFAULT_PROP_YPOS 0.5
59 #define DEFAULT_PROP_WRAP_MODE GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
60 #define DEFAULT_PROP_FONT_DESC ""
61 #define DEFAULT_PROP_SILENT FALSE
62 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
63 #define DEFAULT_PROP_WAIT_TEXT TRUE
64 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
65 #define DEFAULT_PROP_VERTICAL_RENDER FALSE
66 #define DEFAULT_PROP_DRAW_SHADOW TRUE
67 #define DEFAULT_PROP_DRAW_OUTLINE TRUE
68 #define DEFAULT_PROP_COLOR 0xffffffff
69 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
70 #define DEFAULT_PROP_SHADING_VALUE 80
71 #define DEFAULT_PROP_TEXT_X 0
72 #define DEFAULT_PROP_TEXT_Y 0
73 #define DEFAULT_PROP_TEXT_WIDTH 1
74 #define DEFAULT_PROP_TEXT_HEIGHT 1
76 #define MINIMUM_OUTLINE_OFFSET 1.0
77 #define DEFAULT_SCALE_BASIS 640
100 PROP_AUTO_ADJUST_SIZE,
101 PROP_VERTICAL_RENDER,
113 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
115 #define BASE_TEXT_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
117 #define BASE_TEXT_OVERLAY_ALL_CAPS BASE_TEXT_OVERLAY_CAPS ";" \
118 GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
120 static GstStaticCaps sw_template_caps =
121 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
123 static GstStaticPadTemplate src_template_factory =
124 GST_STATIC_PAD_TEMPLATE ("src",
127 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
130 static GstStaticPadTemplate video_sink_template_factory =
131 GST_STATIC_PAD_TEMPLATE ("video_sink",
134 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
137 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
139 gst_base_text_overlay_valign_get_type (void)
141 static GType base_text_overlay_valign_type = 0;
142 static const GEnumValue base_text_overlay_valign[] = {
143 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
144 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
145 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
146 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position",
147 "Absolute position clamped to canvas"},
148 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
149 {GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE, "absolute", "Absolute position"},
153 if (!base_text_overlay_valign_type) {
154 base_text_overlay_valign_type =
155 g_enum_register_static ("GstBaseTextOverlayVAlign",
156 base_text_overlay_valign);
158 return base_text_overlay_valign_type;
161 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
163 gst_base_text_overlay_halign_get_type (void)
165 static GType base_text_overlay_halign_type = 0;
166 static const GEnumValue base_text_overlay_halign[] = {
167 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
168 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
169 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
170 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position",
171 "Absolute position clamped to canvas"},
172 {GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE, "absolute", "Absolute position"},
176 if (!base_text_overlay_halign_type) {
177 base_text_overlay_halign_type =
178 g_enum_register_static ("GstBaseTextOverlayHAlign",
179 base_text_overlay_halign);
181 return base_text_overlay_halign_type;
185 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
187 gst_base_text_overlay_wrap_mode_get_type (void)
189 static GType base_text_overlay_wrap_mode_type = 0;
190 static const GEnumValue base_text_overlay_wrap_mode[] = {
191 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
192 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
193 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
194 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
198 if (!base_text_overlay_wrap_mode_type) {
199 base_text_overlay_wrap_mode_type =
200 g_enum_register_static ("GstBaseTextOverlayWrapMode",
201 base_text_overlay_wrap_mode);
203 return base_text_overlay_wrap_mode_type;
206 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
208 gst_base_text_overlay_line_align_get_type (void)
210 static GType base_text_overlay_line_align_type = 0;
211 static const GEnumValue base_text_overlay_line_align[] = {
212 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
213 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
214 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
218 if (!base_text_overlay_line_align_type) {
219 base_text_overlay_line_align_type =
220 g_enum_register_static ("GstBaseTextOverlayLineAlign",
221 base_text_overlay_line_align);
223 return base_text_overlay_line_align_type;
226 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
227 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
228 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
229 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
230 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
231 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
232 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
234 static GstElementClass *parent_class = NULL;
235 static void gst_base_text_overlay_base_init (gpointer g_class);
236 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
237 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
238 GstBaseTextOverlayClass * klass);
240 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
241 element, GstStateChange transition);
243 static GstCaps *gst_base_text_overlay_get_videosink_caps (GstPad * pad,
244 GstBaseTextOverlay * overlay, GstCaps * filter);
245 static GstCaps *gst_base_text_overlay_get_src_caps (GstPad * pad,
246 GstBaseTextOverlay * overlay, GstCaps * filter);
247 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
249 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
251 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
252 GstObject * parent, GstEvent * event);
253 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
254 GstObject * parent, GstQuery * query);
256 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
257 GstObject * parent, GstEvent * event);
258 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
259 GstObject * parent, GstQuery * query);
260 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
261 GstObject * parent, GstBuffer * buffer);
263 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
264 GstObject * parent, GstEvent * event);
265 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
266 GstObject * parent, GstBuffer * buffer);
267 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
268 GstObject * parent, GstPad * peer);
269 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
271 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
273 static void gst_base_text_overlay_finalize (GObject * object);
274 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
275 const GValue * value, GParamSpec * pspec);
276 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
277 GValue * value, GParamSpec * pspec);
280 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
281 PangoFontDescription * desc);
282 static gboolean gst_base_text_overlay_can_handle_caps (GstCaps * incaps);
285 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay);
288 gst_base_text_overlay_get_type (void)
290 static GType type = 0;
292 if (g_once_init_enter ((gsize *) & type)) {
293 static const GTypeInfo info = {
294 sizeof (GstBaseTextOverlayClass),
295 (GBaseInitFunc) gst_base_text_overlay_base_init,
297 (GClassInitFunc) gst_base_text_overlay_class_init,
300 sizeof (GstBaseTextOverlay),
302 (GInstanceInitFunc) gst_base_text_overlay_init,
305 g_once_init_leave ((gsize *) & type,
306 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
314 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
315 GstBuffer * video_frame)
317 return g_strdup (overlay->default_text);
321 gst_base_text_overlay_base_init (gpointer g_class)
323 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
324 PangoFontMap *fontmap;
326 /* Only lock for the subclasses here, the base class
327 * doesn't have this mutex yet and it's not necessary
329 if (klass->pango_lock)
330 g_mutex_lock (klass->pango_lock);
331 fontmap = pango_cairo_font_map_get_default ();
332 klass->pango_context =
333 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
334 pango_context_set_base_gravity (klass->pango_context, PANGO_GRAVITY_SOUTH);
335 if (klass->pango_lock)
336 g_mutex_unlock (klass->pango_lock);
340 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
342 GObjectClass *gobject_class;
343 GstElementClass *gstelement_class;
345 gobject_class = (GObjectClass *) klass;
346 gstelement_class = (GstElementClass *) klass;
348 parent_class = g_type_class_peek_parent (klass);
350 gobject_class->finalize = gst_base_text_overlay_finalize;
351 gobject_class->set_property = gst_base_text_overlay_set_property;
352 gobject_class->get_property = gst_base_text_overlay_get_property;
354 gst_element_class_add_pad_template (gstelement_class,
355 gst_static_pad_template_get (&src_template_factory));
356 gst_element_class_add_pad_template (gstelement_class,
357 gst_static_pad_template_get (&video_sink_template_factory));
359 gstelement_class->change_state =
360 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
362 klass->pango_lock = g_slice_new (GMutex);
363 g_mutex_init (klass->pango_lock);
365 klass->get_text = gst_base_text_overlay_get_text;
367 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
368 g_param_spec_string ("text", "text",
369 "Text to be display.", DEFAULT_PROP_TEXT,
370 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
371 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
372 g_param_spec_boolean ("shaded-background", "shaded background",
373 "Whether to shade the background under the text area",
374 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
375 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING_VALUE,
376 g_param_spec_uint ("shading-value", "background shading value",
377 "Shading value to apply if shaded-background is true", 1, 255,
378 DEFAULT_PROP_SHADING_VALUE,
379 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
380 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
381 g_param_spec_enum ("valignment", "vertical alignment",
382 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
383 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
384 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
385 g_param_spec_enum ("halignment", "horizontal alignment",
386 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
387 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
388 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
389 g_param_spec_int ("xpad", "horizontal paddding",
390 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
391 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
392 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
393 g_param_spec_int ("ypad", "vertical padding",
394 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
395 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
396 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
397 g_param_spec_int ("deltax", "X position modifier",
398 "Shift X position to the left or to the right. Unit is pixels.",
399 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
400 GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
401 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
402 g_param_spec_int ("deltay", "Y position modifier",
403 "Shift Y position up or down. Unit is pixels.",
404 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAY,
405 GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
408 * GstBaseTextOverlay:text-x:
410 * Resulting X position of font rendering.
412 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_X,
413 g_param_spec_int ("text-x", "horizontal position.",
414 "Resulting X position of font rendering.", -G_MAXINT,
415 G_MAXINT, DEFAULT_PROP_TEXT_X, G_PARAM_READABLE));
418 * GstBaseTextOverlay:text-y:
420 * Resulting Y position of font rendering.
422 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_Y,
423 g_param_spec_int ("text-y", "vertical position",
424 "Resulting X position of font rendering.", -G_MAXINT,
425 G_MAXINT, DEFAULT_PROP_TEXT_Y, G_PARAM_READABLE));
428 * GstBaseTextOverlay:text-width:
430 * Resulting width of font rendering.
432 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_WIDTH,
433 g_param_spec_uint ("text-width", "width",
434 "Resulting width of font rendering",
435 0, G_MAXINT, DEFAULT_PROP_TEXT_WIDTH, G_PARAM_READABLE));
438 * GstBaseTextOverlay:text-height:
440 * Resulting height of font rendering.
442 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_HEIGHT,
443 g_param_spec_uint ("text-height", "height",
444 "Resulting height of font rendering", 0,
445 G_MAXINT, DEFAULT_PROP_TEXT_HEIGHT, G_PARAM_READABLE));
448 * GstBaseTextOverlay:xpos:
450 * Horizontal position of the rendered text when using positioned alignment.
452 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
453 g_param_spec_double ("xpos", "horizontal position",
454 "Horizontal position when using clamped position alignment", 0, 1.0,
456 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
458 * GstBaseTextOverlay:ypos:
460 * Vertical position of the rendered text when using positioned alignment.
462 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
463 g_param_spec_double ("ypos", "vertical position",
464 "Vertical position when using clamped position alignment", 0, 1.0,
466 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
469 * GstBaseTextOverlay:x-absolute:
471 * Horizontal position of the rendered text when using absolute alignment.
473 * Maps the text area to be exactly inside of video canvas for [0, 0] - [1, 1]:
475 * [0, 0]: Top-Lefts of video and text are aligned
476 * [0.5, 0.5]: Centers are aligned
477 * [1, 1]: Bottom-Rights are aligned
479 * Values beyond [0, 0] - [1, 1] place the text outside of the video canvas.
483 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X_ABSOLUTE,
484 g_param_spec_double ("x-absolute", "horizontal position",
485 "Horizontal position when using absolute alignment", -G_MAXDOUBLE,
486 G_MAXDOUBLE, DEFAULT_PROP_XPOS,
487 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
489 * GstBaseTextOverlay:y-absolute:
493 * Vertical position of the rendered text when using absolute alignment.
497 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y_ABSOLUTE,
498 g_param_spec_double ("y-absolute", "vertical position",
499 "Vertical position when using absolute alignment", -G_MAXDOUBLE,
500 G_MAXDOUBLE, DEFAULT_PROP_YPOS,
501 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
503 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
504 g_param_spec_enum ("wrap-mode", "wrap mode",
505 "Whether to wrap the text and if so how.",
506 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
507 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
508 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
509 g_param_spec_string ("font-desc", "font description",
510 "Pango font description of font to be used for rendering. "
511 "See documentation of pango_font_description_from_string "
512 "for syntax.", DEFAULT_PROP_FONT_DESC,
513 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
515 * GstBaseTextOverlay:color:
517 * Color of the rendered text.
519 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
520 g_param_spec_uint ("color", "Color",
521 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
523 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
525 * GstTextOverlay:outline-color:
527 * Color of the outline of the rendered text.
529 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
530 g_param_spec_uint ("outline-color", "Text Outline Color",
531 "Color to use for outline the text (big-endian ARGB).", 0,
532 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
533 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
536 * GstBaseTextOverlay:line-alignment:
538 * Alignment of text lines relative to each other (for multi-line text)
540 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
541 g_param_spec_enum ("line-alignment", "line alignment",
542 "Alignment of text lines relative to each other.",
543 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
544 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
546 * GstBaseTextOverlay:silent:
548 * If set, no text is rendered. Useful to switch off text rendering
549 * temporarily without removing the textoverlay element from the pipeline.
551 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
552 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
553 g_param_spec_boolean ("silent", "silent",
554 "Whether to render the text string",
556 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
558 * GstBaseTextOverlay:draw-shadow:
560 * If set, a text shadow is drawn.
564 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_SHADOW,
565 g_param_spec_boolean ("draw-shadow", "draw-shadow",
566 "Whether to draw shadow",
567 DEFAULT_PROP_DRAW_SHADOW,
568 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
570 * GstBaseTextOverlay:draw-outline:
572 * If set, an outline is drawn.
576 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_OUTLINE,
577 g_param_spec_boolean ("draw-outline", "draw-outline",
578 "Whether to draw outline",
579 DEFAULT_PROP_DRAW_OUTLINE,
580 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
582 * GstBaseTextOverlay:wait-text:
584 * If set, the video will block until a subtitle is received on the text pad.
585 * If video and subtitles are sent in sync, like from the same demuxer, this
586 * property should be set.
588 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
589 g_param_spec_boolean ("wait-text", "Wait Text",
590 "Whether to wait for subtitles",
591 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
593 g_object_class_install_property (G_OBJECT_CLASS (klass),
594 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
595 "Automatically adjust font size to screen-size.",
596 DEFAULT_PROP_AUTO_ADJUST_SIZE,
597 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
599 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
600 g_param_spec_boolean ("vertical-render", "vertical render",
601 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
602 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
606 gst_base_text_overlay_finalize (GObject * object)
608 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
610 g_free (overlay->default_text);
612 if (overlay->composition) {
613 gst_video_overlay_composition_unref (overlay->composition);
614 overlay->composition = NULL;
617 if (overlay->text_image) {
618 gst_buffer_unref (overlay->text_image);
619 overlay->text_image = NULL;
622 if (overlay->layout) {
623 g_object_unref (overlay->layout);
624 overlay->layout = NULL;
627 if (overlay->text_buffer) {
628 gst_buffer_unref (overlay->text_buffer);
629 overlay->text_buffer = NULL;
632 g_mutex_clear (&overlay->lock);
633 g_cond_clear (&overlay->cond);
635 G_OBJECT_CLASS (parent_class)->finalize (object);
639 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
640 GstBaseTextOverlayClass * klass)
642 GstPadTemplate *template;
643 PangoFontDescription *desc;
646 template = gst_static_pad_template_get (&video_sink_template_factory);
647 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
648 gst_object_unref (template);
649 gst_pad_set_event_function (overlay->video_sinkpad,
650 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
651 gst_pad_set_chain_function (overlay->video_sinkpad,
652 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
653 gst_pad_set_query_function (overlay->video_sinkpad,
654 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
655 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
656 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
659 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
663 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
665 gst_pad_set_event_function (overlay->text_sinkpad,
666 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
667 gst_pad_set_chain_function (overlay->text_sinkpad,
668 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
669 gst_pad_set_link_function (overlay->text_sinkpad,
670 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
671 gst_pad_set_unlink_function (overlay->text_sinkpad,
672 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
673 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
677 template = gst_static_pad_template_get (&src_template_factory);
678 overlay->srcpad = gst_pad_new_from_template (template, "src");
679 gst_object_unref (template);
680 gst_pad_set_event_function (overlay->srcpad,
681 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
682 gst_pad_set_query_function (overlay->srcpad,
683 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
684 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
686 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
688 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
689 (overlay)->pango_context);
691 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
692 (overlay)->pango_context);
693 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
695 overlay->color = DEFAULT_PROP_COLOR;
696 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
697 overlay->halign = DEFAULT_PROP_HALIGNMENT;
698 overlay->valign = DEFAULT_PROP_VALIGNMENT;
699 overlay->xpad = DEFAULT_PROP_XPAD;
700 overlay->ypad = DEFAULT_PROP_YPAD;
701 overlay->deltax = DEFAULT_PROP_DELTAX;
702 overlay->deltay = DEFAULT_PROP_DELTAY;
703 overlay->xpos = DEFAULT_PROP_XPOS;
704 overlay->ypos = DEFAULT_PROP_YPOS;
706 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
708 overlay->want_shading = DEFAULT_PROP_SHADING;
709 overlay->shading_value = DEFAULT_PROP_SHADING_VALUE;
710 overlay->silent = DEFAULT_PROP_SILENT;
711 overlay->draw_shadow = DEFAULT_PROP_DRAW_SHADOW;
712 overlay->draw_outline = DEFAULT_PROP_DRAW_OUTLINE;
713 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
714 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
716 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
717 overlay->need_render = TRUE;
718 overlay->text_image = NULL;
719 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
721 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
722 pango_layout_set_alignment (overlay->layout,
723 (PangoAlignment) overlay->line_align);
725 overlay->text_buffer = NULL;
726 overlay->text_linked = FALSE;
728 overlay->composition = NULL;
729 overlay->upstream_composition = NULL;
734 overlay->window_width = 1;
735 overlay->window_height = 1;
737 overlay->text_width = DEFAULT_PROP_TEXT_WIDTH;
738 overlay->text_height = DEFAULT_PROP_TEXT_HEIGHT;
740 overlay->text_x = DEFAULT_PROP_TEXT_X;
741 overlay->text_y = DEFAULT_PROP_TEXT_Y;
743 overlay->render_width = 1;
744 overlay->render_height = 1;
745 overlay->render_scale = 1.0l;
747 g_mutex_init (&overlay->lock);
748 g_cond_init (&overlay->cond);
749 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
750 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
754 gst_base_text_overlay_set_wrap_mode (GstBaseTextOverlay * overlay, gint width)
756 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
757 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
758 pango_layout_set_width (overlay->layout, -1);
760 width = width * PANGO_SCALE;
762 GST_DEBUG_OBJECT (overlay, "Set layout width %d", width);
763 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
764 pango_layout_set_width (overlay->layout, width);
767 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
771 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
773 GstStructure *structure;
776 structure = gst_caps_get_structure (caps, 0);
777 format = gst_structure_get_string (structure, "format");
778 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
783 /* only negotiate/query video overlay composition support for now */
785 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay, GstCaps * caps)
787 gboolean upstream_has_meta = FALSE;
788 gboolean caps_has_meta = FALSE;
789 gboolean alloc_has_meta = FALSE;
790 gboolean attach = FALSE;
794 GstCaps *overlay_caps;
798 GST_DEBUG_OBJECT (overlay, "performing negotiation");
800 /* Clear any pending reconfigure to avoid negotiating twice */
801 gst_pad_check_reconfigure (overlay->srcpad);
804 caps = gst_pad_get_current_caps (overlay->video_sinkpad);
808 if (!caps || gst_caps_is_empty (caps))
811 /* Check if upstream caps have meta */
812 if ((f = gst_caps_get_features (caps, 0))) {
813 upstream_has_meta = gst_caps_features_contains (f,
814 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
817 /* Initialize dimensions */
818 width = overlay->width;
819 height = overlay->height;
821 if (upstream_has_meta) {
822 overlay_caps = gst_caps_ref (caps);
826 /* BaseTransform requires caps for the allocation query to work */
827 overlay_caps = gst_caps_copy (caps);
828 f = gst_caps_get_features (overlay_caps, 0);
829 gst_caps_features_add (f,
830 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
832 /* Then check if downstream accept overlay composition in caps */
833 /* FIXME: We should probably check if downstream *prefers* the
834 * overlay meta, and only enforce usage of it if we can't handle
835 * the format ourselves and thus would have to drop the overlays.
836 * Otherwise we should prefer what downstream wants here.
838 peercaps = gst_pad_peer_query_caps (overlay->srcpad, NULL);
839 caps_has_meta = gst_caps_can_intersect (peercaps, overlay_caps);
840 gst_caps_unref (peercaps);
842 GST_DEBUG ("caps have overlay meta %d", caps_has_meta);
845 if (upstream_has_meta || caps_has_meta) {
846 /* Send caps immediatly, it's needed by GstBaseTransform to get a reply
847 * from allocation query */
848 ret = gst_pad_set_caps (overlay->srcpad, overlay_caps);
850 /* First check if the allocation meta has compositon */
851 query = gst_query_new_allocation (overlay_caps, FALSE);
853 if (!gst_pad_peer_query (overlay->srcpad, query)) {
854 /* no problem, we use the query defaults */
855 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
857 /* In case we were flushing, mark reconfigure and fail this method,
858 * will make it retry */
859 if (overlay->video_flushing)
863 alloc_has_meta = gst_query_find_allocation_meta (query,
864 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, &alloc_index);
866 GST_DEBUG ("sink alloc has overlay meta %d", alloc_has_meta);
868 if (alloc_has_meta) {
869 const GstStructure *params;
871 gst_query_parse_nth_allocation_meta (query, alloc_index, ¶ms);
873 if (gst_structure_get (params, "width", G_TYPE_UINT, &width,
874 "height", G_TYPE_UINT, &height, NULL)) {
875 GST_DEBUG ("received window size: %dx%d", width, height);
876 g_assert (width != 0 && height != 0);
881 gst_query_unref (query);
884 /* Update render size if needed */
885 overlay->window_width = width;
886 overlay->window_height = height;
887 gst_base_text_overlay_update_render_size (overlay);
889 /* For backward compatbility, we will prefer bliting if downstream
890 * allocation does not support the meta. In other case we will prefer
891 * attaching, and will fail the negotiation in the unlikely case we are
892 * force to blit, but format isn't supported. */
894 if (upstream_has_meta) {
896 } else if (caps_has_meta) {
897 if (alloc_has_meta) {
900 /* Don't attach unless we cannot handle the format */
901 attach = !gst_base_text_overlay_can_handle_caps (caps);
904 ret = gst_base_text_overlay_can_handle_caps (caps);
907 /* If we attach, then pick the overlay caps */
909 GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, overlay_caps);
910 /* Caps where already sent */
912 GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps);
913 ret = gst_pad_set_caps (overlay->srcpad, caps);
916 overlay->attach_compo_to_buffer = attach;
919 GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure");
920 gst_pad_mark_reconfigure (overlay->srcpad);
923 gst_caps_unref (overlay_caps);
924 gst_caps_unref (caps);
931 gst_caps_unref (caps);
937 gst_base_text_overlay_can_handle_caps (GstCaps * incaps)
941 static GstStaticCaps static_caps = GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
943 caps = gst_static_caps_get (&static_caps);
944 ret = gst_caps_is_subset (incaps, caps);
945 gst_caps_unref (caps);
951 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
954 gboolean ret = FALSE;
956 if (!gst_video_info_from_caps (&info, caps))
959 /* Render again if size have changed */
960 if (GST_VIDEO_INFO_WIDTH (&info) != GST_VIDEO_INFO_WIDTH (&overlay->info) ||
961 GST_VIDEO_INFO_HEIGHT (&info) != GST_VIDEO_INFO_HEIGHT (&overlay->info))
962 overlay->need_render = TRUE;
964 overlay->info = info;
965 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
966 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
967 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
969 ret = gst_base_text_overlay_negotiate (overlay, caps);
971 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
973 if (!overlay->attach_compo_to_buffer &&
974 !gst_base_text_overlay_can_handle_caps (caps)) {
975 GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps);
978 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
985 GST_DEBUG_OBJECT (overlay, "could not parse caps");
991 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
992 const GValue * value, GParamSpec * pspec)
994 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
996 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
999 g_free (overlay->default_text);
1000 overlay->default_text = g_value_dup_string (value);
1003 overlay->want_shading = g_value_get_boolean (value);
1006 overlay->xpad = g_value_get_int (value);
1009 overlay->ypad = g_value_get_int (value);
1012 overlay->deltax = g_value_get_int (value);
1015 overlay->deltay = g_value_get_int (value);
1018 overlay->xpos = g_value_get_double (value);
1021 overlay->ypos = g_value_get_double (value);
1023 case PROP_X_ABSOLUTE:
1024 overlay->xpos = g_value_get_double (value);
1026 case PROP_Y_ABSOLUTE:
1027 overlay->ypos = g_value_get_double (value);
1029 case PROP_VALIGNMENT:
1030 overlay->valign = g_value_get_enum (value);
1032 case PROP_HALIGNMENT:
1033 overlay->halign = g_value_get_enum (value);
1035 case PROP_WRAP_MODE:
1036 overlay->wrap_mode = g_value_get_enum (value);
1038 case PROP_FONT_DESC:
1040 PangoFontDescription *desc;
1041 const gchar *fontdesc_str;
1043 fontdesc_str = g_value_get_string (value);
1044 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1045 desc = pango_font_description_from_string (fontdesc_str);
1047 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
1048 pango_layout_set_font_description (overlay->layout, desc);
1049 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
1050 pango_font_description_free (desc);
1052 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
1055 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1059 overlay->color = g_value_get_uint (value);
1061 case PROP_OUTLINE_COLOR:
1062 overlay->outline_color = g_value_get_uint (value);
1065 overlay->silent = g_value_get_boolean (value);
1067 case PROP_DRAW_SHADOW:
1068 overlay->draw_shadow = g_value_get_boolean (value);
1070 case PROP_DRAW_OUTLINE:
1071 overlay->draw_outline = g_value_get_boolean (value);
1073 case PROP_LINE_ALIGNMENT:
1074 overlay->line_align = g_value_get_enum (value);
1075 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1076 pango_layout_set_alignment (overlay->layout,
1077 (PangoAlignment) overlay->line_align);
1078 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1080 case PROP_WAIT_TEXT:
1081 overlay->wait_text = g_value_get_boolean (value);
1083 case PROP_AUTO_ADJUST_SIZE:
1084 overlay->auto_adjust_size = g_value_get_boolean (value);
1086 case PROP_VERTICAL_RENDER:
1087 overlay->use_vertical_render = g_value_get_boolean (value);
1088 if (overlay->use_vertical_render) {
1089 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1090 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1091 overlay->line_align = GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT;
1092 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1093 pango_layout_set_alignment (overlay->layout,
1094 (PangoAlignment) overlay->line_align);
1095 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1098 case PROP_SHADING_VALUE:
1099 overlay->shading_value = g_value_get_uint (value);
1102 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1106 overlay->need_render = TRUE;
1107 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1111 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
1112 GValue * value, GParamSpec * pspec)
1114 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
1116 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1119 g_value_set_string (value, overlay->default_text);
1122 g_value_set_boolean (value, overlay->want_shading);
1125 g_value_set_int (value, overlay->xpad);
1128 g_value_set_int (value, overlay->ypad);
1131 g_value_set_int (value, overlay->deltax);
1134 g_value_set_int (value, overlay->deltay);
1137 g_value_set_double (value, overlay->xpos);
1140 g_value_set_double (value, overlay->ypos);
1142 case PROP_X_ABSOLUTE:
1143 g_value_set_double (value, overlay->xpos);
1145 case PROP_Y_ABSOLUTE:
1146 g_value_set_double (value, overlay->ypos);
1148 case PROP_VALIGNMENT:
1149 g_value_set_enum (value, overlay->valign);
1151 case PROP_HALIGNMENT:
1152 g_value_set_enum (value, overlay->halign);
1154 case PROP_WRAP_MODE:
1155 g_value_set_enum (value, overlay->wrap_mode);
1158 g_value_set_boolean (value, overlay->silent);
1160 case PROP_DRAW_SHADOW:
1161 g_value_set_boolean (value, overlay->draw_shadow);
1163 case PROP_DRAW_OUTLINE:
1164 g_value_set_boolean (value, overlay->draw_outline);
1166 case PROP_LINE_ALIGNMENT:
1167 g_value_set_enum (value, overlay->line_align);
1169 case PROP_WAIT_TEXT:
1170 g_value_set_boolean (value, overlay->wait_text);
1172 case PROP_AUTO_ADJUST_SIZE:
1173 g_value_set_boolean (value, overlay->auto_adjust_size);
1175 case PROP_VERTICAL_RENDER:
1176 g_value_set_boolean (value, overlay->use_vertical_render);
1179 g_value_set_uint (value, overlay->color);
1181 case PROP_OUTLINE_COLOR:
1182 g_value_set_uint (value, overlay->outline_color);
1184 case PROP_SHADING_VALUE:
1185 g_value_set_uint (value, overlay->shading_value);
1187 case PROP_FONT_DESC:
1189 const PangoFontDescription *desc;
1191 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1192 desc = pango_layout_get_font_description (overlay->layout);
1194 g_value_set_string (value, "");
1196 g_value_take_string (value, pango_font_description_to_string (desc));
1198 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1202 g_value_set_int (value, overlay->text_x);
1205 g_value_set_int (value, overlay->text_y);
1207 case PROP_TEXT_WIDTH:
1208 g_value_set_uint (value, overlay->text_width);
1210 case PROP_TEXT_HEIGHT:
1211 g_value_set_uint (value, overlay->text_height);
1214 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1218 overlay->need_render = TRUE;
1219 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1223 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1226 gboolean ret = FALSE;
1227 GstBaseTextOverlay *overlay;
1229 overlay = GST_BASE_TEXT_OVERLAY (parent);
1231 switch (GST_QUERY_TYPE (query)) {
1232 case GST_QUERY_CAPS:
1234 GstCaps *filter, *caps;
1236 gst_query_parse_caps (query, &filter);
1237 caps = gst_base_text_overlay_get_src_caps (pad, overlay, filter);
1238 gst_query_set_caps_result (query, caps);
1239 gst_caps_unref (caps);
1244 ret = gst_pad_query_default (pad, parent, query);
1252 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay)
1254 gdouble video_aspect = (gdouble) overlay->width / (gdouble) overlay->height;
1255 gdouble window_aspect = (gdouble) overlay->window_width /
1256 (gdouble) overlay->window_height;
1258 guint text_buffer_width = 0;
1259 guint text_buffer_height = 0;
1261 if (video_aspect >= window_aspect) {
1262 text_buffer_width = overlay->window_width;
1263 text_buffer_height = window_aspect * overlay->window_height / video_aspect;
1264 } else if (video_aspect < window_aspect) {
1265 text_buffer_width = video_aspect * overlay->window_width / window_aspect;
1266 text_buffer_height = overlay->window_height;
1269 if ((overlay->render_width == text_buffer_width) &&
1270 (overlay->render_height == text_buffer_height))
1273 overlay->need_render = TRUE;
1274 overlay->render_width = text_buffer_width;
1275 overlay->render_height = text_buffer_height;
1276 overlay->render_scale = (gdouble) overlay->render_width /
1277 (gdouble) overlay->width;
1279 GST_DEBUG ("updating render dimensions %dx%d from stream %dx%d, window %dx%d "
1280 "and render scale %f", overlay->render_width, overlay->render_height,
1281 overlay->width, overlay->height, overlay->window_width,
1282 overlay->window_height, overlay->render_scale);
1286 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1289 GstBaseTextOverlay *overlay;
1292 overlay = GST_BASE_TEXT_OVERLAY (parent);
1294 if (overlay->text_linked) {
1295 gst_event_ref (event);
1296 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1297 gst_pad_push_event (overlay->text_sinkpad, event);
1299 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1306 * gst_base_text_overlay_add_feature_and_intersect:
1308 * Creates a new #GstCaps containing the (given caps +
1309 * given caps feature) + (given caps intersected by the
1312 * Returns: the new #GstCaps
1315 gst_base_text_overlay_add_feature_and_intersect (GstCaps * caps,
1316 const gchar * feature, GstCaps * filter)
1321 new_caps = gst_caps_copy (caps);
1323 caps_size = gst_caps_get_size (new_caps);
1324 for (i = 0; i < caps_size; i++) {
1325 GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
1327 if (!gst_caps_features_is_any (features)) {
1328 gst_caps_features_add (features, feature);
1332 gst_caps_append (new_caps, gst_caps_intersect_full (caps,
1333 filter, GST_CAPS_INTERSECT_FIRST));
1339 * gst_base_text_overlay_intersect_by_feature:
1341 * Creates a new #GstCaps based on the following filtering rule.
1343 * For each individual caps contained in given caps, if the
1344 * caps uses the given caps feature, keep a version of the caps
1345 * with the feature and an another one without. Otherwise, intersect
1346 * the caps with the given filter.
1348 * Returns: the new #GstCaps
1351 gst_base_text_overlay_intersect_by_feature (GstCaps * caps,
1352 const gchar * feature, GstCaps * filter)
1357 new_caps = gst_caps_new_empty ();
1359 caps_size = gst_caps_get_size (caps);
1360 for (i = 0; i < caps_size; i++) {
1361 GstStructure *caps_structure = gst_caps_get_structure (caps, i);
1362 GstCapsFeatures *caps_features =
1363 gst_caps_features_copy (gst_caps_get_features (caps, i));
1364 GstCaps *filtered_caps;
1365 GstCaps *simple_caps =
1366 gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
1367 gst_caps_set_features (simple_caps, 0, caps_features);
1369 if (gst_caps_features_contains (caps_features, feature)) {
1370 gst_caps_append (new_caps, gst_caps_copy (simple_caps));
1372 gst_caps_features_remove (caps_features, feature);
1373 filtered_caps = gst_caps_ref (simple_caps);
1375 filtered_caps = gst_caps_intersect_full (simple_caps, filter,
1376 GST_CAPS_INTERSECT_FIRST);
1379 gst_caps_unref (simple_caps);
1380 gst_caps_append (new_caps, filtered_caps);
1387 gst_base_text_overlay_get_videosink_caps (GstPad * pad,
1388 GstBaseTextOverlay * overlay, GstCaps * filter)
1390 GstPad *srcpad = overlay->srcpad;
1391 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1393 if (G_UNLIKELY (!overlay))
1394 return gst_pad_get_pad_template_caps (pad);
1397 /* filter caps + composition feature + filter caps
1398 * filtered by the software caps. */
1399 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1400 overlay_filter = gst_base_text_overlay_add_feature_and_intersect (filter,
1401 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1402 gst_caps_unref (sw_caps);
1404 GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
1408 peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
1411 gst_caps_unref (overlay_filter);
1415 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
1417 if (gst_caps_is_any (peer_caps)) {
1418 /* if peer returns ANY caps, return filtered src pad template caps */
1419 caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
1422 /* duplicate caps which contains the composition into one version with
1423 * the meta and one without. Filter the other caps by the software caps */
1424 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1425 caps = gst_base_text_overlay_intersect_by_feature (peer_caps,
1426 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1427 gst_caps_unref (sw_caps);
1430 gst_caps_unref (peer_caps);
1433 /* no peer, our padtemplate is enough then */
1434 caps = gst_pad_get_pad_template_caps (pad);
1438 GstCaps *intersection = gst_caps_intersect_full (filter, caps,
1439 GST_CAPS_INTERSECT_FIRST);
1440 gst_caps_unref (caps);
1441 caps = intersection;
1444 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1450 gst_base_text_overlay_get_src_caps (GstPad * pad, GstBaseTextOverlay * overlay,
1453 GstPad *sinkpad = overlay->video_sinkpad;
1454 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1456 if (G_UNLIKELY (!overlay))
1457 return gst_pad_get_pad_template_caps (pad);
1460 /* duplicate filter caps which contains the composition into one version
1461 * with the meta and one without. Filter the other caps by the software
1463 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1465 gst_base_text_overlay_intersect_by_feature (filter,
1466 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1467 gst_caps_unref (sw_caps);
1470 peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
1473 gst_caps_unref (overlay_filter);
1477 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
1479 if (gst_caps_is_any (peer_caps)) {
1481 /* if peer returns ANY caps, return filtered sink pad template caps */
1482 caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
1486 /* return upstream caps + composition feature + upstream caps
1487 * filtered by the software caps. */
1488 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1489 caps = gst_base_text_overlay_add_feature_and_intersect (peer_caps,
1490 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1491 gst_caps_unref (sw_caps);
1494 gst_caps_unref (peer_caps);
1497 /* no peer, our padtemplate is enough then */
1498 caps = gst_pad_get_pad_template_caps (pad);
1502 GstCaps *intersection;
1505 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1506 gst_caps_unref (caps);
1507 caps = intersection;
1509 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1515 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1516 PangoFontDescription * desc)
1518 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1519 overlay->shadow_offset = (double) (font_size) / 13.0;
1520 overlay->outline_offset = (double) (font_size) / 15.0;
1521 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1522 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1526 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1527 gint * xpos, gint * ypos)
1531 width = overlay->logical_rect.width;
1532 height = overlay->logical_rect.height;
1534 *xpos = overlay->ink_rect.x - overlay->logical_rect.x;
1535 switch (overlay->halign) {
1536 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1537 *xpos += overlay->xpad;
1538 *xpos = MAX (0, *xpos);
1540 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1541 *xpos += (overlay->width - width) / 2;
1543 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1544 *xpos += overlay->width - width - overlay->xpad;
1545 *xpos = MIN (overlay->width - overlay->ink_rect.width, *xpos);
1547 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1548 *xpos += (gint) (overlay->width * overlay->xpos) - width / 2;
1549 *xpos = CLAMP (*xpos, 0, overlay->width - overlay->ink_rect.width);
1553 case GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE:
1554 *xpos = (overlay->width - overlay->text_width) * overlay->xpos;
1559 *xpos += overlay->deltax;
1561 *ypos = overlay->ink_rect.y - overlay->logical_rect.y;
1562 switch (overlay->valign) {
1563 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1564 /* This will be the same as baseline, if there is enough padding,
1565 * otherwise it will avoid clipping the text */
1566 *ypos += overlay->height - height - overlay->ypad;
1567 *ypos = MIN (overlay->height - overlay->ink_rect.height, *ypos);
1569 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1570 *ypos += overlay->height - height - overlay->ypad;
1571 /* Don't clip, this would not respect the base line */
1573 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1574 *ypos += overlay->ypad;
1575 *ypos = MAX (0.0, *ypos);
1577 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1578 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1579 *ypos = CLAMP (*ypos, 0, overlay->height - overlay->ink_rect.height);
1581 case GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE:
1582 *ypos = (overlay->height - overlay->text_height) * overlay->ypos;
1584 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1585 *ypos = (overlay->height - height) / 2;
1588 *ypos = overlay->ypad;
1591 *ypos += overlay->deltay;
1593 overlay->text_x = *xpos;
1594 overlay->text_y = *ypos;
1596 GST_DEBUG_OBJECT (overlay, "Placing overlay at (%d, %d)", *xpos, *ypos);
1600 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1603 GstVideoOverlayRectangle *rectangle;
1605 if (overlay->text_image && overlay->text_width != 1) {
1606 gint render_width, render_height;
1608 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1610 render_width = overlay->ink_rect.width;
1611 render_height = overlay->ink_rect.height;
1613 GST_DEBUG ("updating composition for '%s' with window size %dx%d, "
1614 "buffer size %dx%d, render size %dx%d and position (%d, %d)",
1615 overlay->default_text, overlay->window_width, overlay->window_height,
1616 overlay->text_width, overlay->text_height, render_width,
1617 render_height, xpos, ypos);
1619 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1620 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1621 overlay->text_width, overlay->text_height);
1623 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1624 xpos, ypos, render_width, render_height,
1625 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1627 if (overlay->composition)
1628 gst_video_overlay_composition_unref (overlay->composition);
1630 overlay->composition = gst_video_overlay_composition_new (rectangle);
1631 gst_video_overlay_rectangle_unref (rectangle);
1633 if (overlay->upstream_composition) {
1634 guint num_overlays =
1635 gst_video_overlay_composition_n_rectangles
1636 (overlay->upstream_composition);
1638 for (guint i = 0; i < num_overlays; i++) {
1639 GstVideoOverlayRectangle *rectangle;
1641 gst_video_overlay_composition_get_rectangle
1642 (overlay->upstream_composition, i);
1643 gst_video_overlay_composition_add_rectangle (overlay->composition,
1648 } else if (overlay->composition) {
1649 gst_video_overlay_composition_unref (overlay->composition);
1650 overlay->composition = NULL;
1655 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1657 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1665 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1666 const gchar * string, gint textlen)
1669 cairo_surface_t *surface;
1670 PangoRectangle ink_rect, logical_rect;
1671 cairo_matrix_t cairo_matrix;
1672 gint unscaled_width, unscaled_height;
1674 gboolean full_width = FALSE;
1675 double scalef = 1.0;
1677 gdouble shadow_offset = 0.0;
1678 gdouble outline_offset = 0.0;
1679 gint xpad = 0, ypad = 0;
1683 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1685 if (overlay->auto_adjust_size) {
1686 /* 640 pixel is default */
1687 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1690 if (overlay->draw_shadow)
1691 shadow_offset = ceil (overlay->shadow_offset);
1693 /* This value is uses as cairo line width, which is the diameter of a pen
1694 * that is circular. That's why only half of it is used to offset */
1695 if (overlay->draw_outline)
1696 outline_offset = ceil (overlay->outline_offset);
1698 if (overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_LEFT ||
1699 overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT)
1700 xpad = overlay->xpad;
1702 if (overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_TOP ||
1703 overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM)
1704 ypad = overlay->ypad;
1706 pango_layout_set_width (overlay->layout, -1);
1707 /* set text on pango layout */
1708 pango_layout_set_markup (overlay->layout, string, textlen);
1710 /* get subtitle image size */
1711 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1713 unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1714 width = ceil (unscaled_width * scalef);
1717 * subtitle image width can be larger then overlay width
1718 * so rearrange overlay wrap mode.
1720 if (overlay->use_vertical_render) {
1721 if (width + ypad > overlay->height) {
1722 width = overlay->height - ypad;
1725 } else if (width + xpad > overlay->width) {
1726 width = overlay->width - xpad;
1731 unscaled_width = width / scalef;
1732 gst_base_text_overlay_set_wrap_mode (overlay,
1733 unscaled_width - shadow_offset - outline_offset);
1734 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1736 unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1737 width = ceil (unscaled_width * scalef);
1740 unscaled_height = ink_rect.height + shadow_offset + outline_offset;
1741 height = ceil (unscaled_height * scalef);
1743 if (overlay->use_vertical_render) {
1744 if (height + xpad > overlay->width) {
1745 height = overlay->width - xpad;
1746 unscaled_height = width / scalef;
1748 } else if (height + ypad > overlay->height) {
1749 height = overlay->height - ypad;
1750 unscaled_height = height / scalef;
1753 GST_DEBUG_OBJECT (overlay, "Rendering with ink rect (%d, %d) %dx%d and "
1754 "logical rect (%d, %d) %dx%d", ink_rect.x, ink_rect.y, ink_rect.width,
1755 ink_rect.height, logical_rect.x, logical_rect.y, logical_rect.width,
1756 logical_rect.height);
1757 GST_DEBUG_OBJECT (overlay, "Rendering with width %d and height %d "
1758 "(shadow %f, outline %f)", unscaled_width, unscaled_height,
1759 shadow_offset, outline_offset);
1762 /* Save and scale the rectangles so get_pos() can place the text */
1763 overlay->ink_rect.x =
1764 ceil ((ink_rect.x - ceil (outline_offset / 2.0l)) * scalef);
1765 overlay->ink_rect.y =
1766 ceil ((ink_rect.y - ceil (outline_offset / 2.0l)) * scalef);
1767 overlay->ink_rect.width = width;
1768 overlay->ink_rect.height = height;
1770 overlay->logical_rect.x =
1771 ceil ((logical_rect.x - ceil (outline_offset / 2.0l)) * scalef);
1772 overlay->logical_rect.y =
1773 ceil ((logical_rect.y - ceil (outline_offset / 2.0l)) * scalef);
1774 overlay->logical_rect.width =
1775 ceil ((logical_rect.width + shadow_offset + outline_offset) * scalef);
1776 overlay->logical_rect.height =
1777 ceil ((logical_rect.height + shadow_offset + outline_offset) * scalef);
1779 /* flip the rectangle if doing vertical render */
1780 if (overlay->use_vertical_render) {
1781 PangoRectangle tmp = overlay->ink_rect;
1783 overlay->ink_rect.x = tmp.y;
1784 overlay->ink_rect.y = tmp.x;
1785 overlay->ink_rect.width = tmp.height;
1786 overlay->ink_rect.height = tmp.width;
1787 /* We want the top left corect, but we now have the top right */
1788 overlay->ink_rect.x += overlay->ink_rect.width;
1790 tmp = overlay->logical_rect;
1791 overlay->logical_rect.x = tmp.y;
1792 overlay->logical_rect.y = tmp.x;
1793 overlay->logical_rect.width = tmp.height;
1794 overlay->logical_rect.height = tmp.width;
1795 overlay->logical_rect.x += overlay->logical_rect.width;
1798 /* scale to reported window size */
1799 width = ceil (width * overlay->render_scale);
1800 height = ceil (height * overlay->render_scale);
1801 scalef *= overlay->render_scale;
1803 if (width <= 0 || height <= 0) {
1804 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1805 GST_DEBUG_OBJECT (overlay,
1806 "Overlay is outside video frame. Skipping text rendering");
1810 if (unscaled_height <= 0 || unscaled_width <= 0) {
1811 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1812 GST_DEBUG_OBJECT (overlay,
1813 "Overlay is outside video frame. Skipping text rendering");
1816 /* Prepare the transformation matrix. Note that the transformation happens
1817 * in reverse order. So for horizontal text, we will translate and then
1818 * scale. This is important to understand which scale shall be used. */
1819 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1821 if (overlay->use_vertical_render) {
1824 /* tranlate to the center of the image, rotate, and tranlate the rotated
1825 * image back to the right place */
1826 cairo_matrix_translate (&cairo_matrix, unscaled_height / 2.0l,
1827 unscaled_width / 2.0l);
1828 /* 90 degree clockwise rotation which is PI / 2 in radiants */
1829 cairo_matrix_rotate (&cairo_matrix, G_PI_2);
1830 cairo_matrix_translate (&cairo_matrix, -(unscaled_width / 2.0l),
1831 -(unscaled_height / 2.0l));
1833 /* Swap width and height */
1839 cairo_matrix_translate (&cairo_matrix,
1840 ceil (outline_offset / 2.0l) - ink_rect.x,
1841 ceil (outline_offset / 2.0l) - ink_rect.y);
1843 /* reallocate overlay buffer */
1844 buffer = gst_buffer_new_and_alloc (4 * width * height);
1845 gst_buffer_replace (&overlay->text_image, buffer);
1846 gst_buffer_unref (buffer);
1848 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1849 surface = cairo_image_surface_create_for_data (map.data,
1850 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1851 cr = cairo_create (surface);
1854 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1857 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1859 /* apply transformations */
1860 cairo_set_matrix (cr, &cairo_matrix);
1862 /* FIXME: We use show_layout everywhere except for the surface
1863 * because it's really faster and internally does all kinds of
1864 * caching. Unfortunately we have to paint to a cairo path for
1865 * the outline and this is slow. Once Pango supports user fonts
1866 * we should use them, see
1867 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1869 * Idea would the be, to create a cairo user font that
1870 * does shadow, outline, text painting in the
1871 * render_glyph function.
1874 /* draw shadow text */
1875 if (overlay->draw_shadow) {
1876 PangoAttrList *origin_attr, *filtered_attr, *temp_attr;
1878 /* Store a ref on the original attributes for later restoration */
1880 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1881 /* Take a copy of the original attributes, because pango_attr_list_filter
1882 * modifies the passed list */
1883 temp_attr = pango_attr_list_copy (origin_attr);
1885 pango_attr_list_filter (temp_attr,
1886 gst_text_overlay_filter_foreground_attr, NULL);
1887 pango_attr_list_unref (temp_attr);
1890 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1891 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1892 pango_layout_set_attributes (overlay->layout, filtered_attr);
1893 pango_cairo_show_layout (cr, overlay->layout);
1894 pango_layout_set_attributes (overlay->layout, origin_attr);
1895 pango_attr_list_unref (filtered_attr);
1896 pango_attr_list_unref (origin_attr);
1900 /* draw outline text */
1901 if (overlay->draw_outline) {
1902 a = (overlay->outline_color >> 24) & 0xff;
1903 r = (overlay->outline_color >> 16) & 0xff;
1904 g = (overlay->outline_color >> 8) & 0xff;
1905 b = (overlay->outline_color >> 0) & 0xff;
1908 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1909 cairo_set_line_width (cr, overlay->outline_offset);
1910 pango_cairo_layout_path (cr, overlay->layout);
1915 a = (overlay->color >> 24) & 0xff;
1916 r = (overlay->color >> 16) & 0xff;
1917 g = (overlay->color >> 8) & 0xff;
1918 b = (overlay->color >> 0) & 0xff;
1922 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1923 pango_cairo_show_layout (cr, overlay->layout);
1927 cairo_surface_destroy (surface);
1928 gst_buffer_unmap (buffer, &map);
1930 overlay->text_width = width;
1932 overlay->text_height = height;
1933 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1935 gst_base_text_overlay_set_composition (overlay);
1939 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1940 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1942 gint i, j, dest_stride;
1945 dest_stride = dest->info.stride[0];
1946 dest_ptr = dest->data[0];
1948 for (i = y0; i < y1; ++i) {
1949 for (j = x0; j < x1; ++j) {
1950 gint y = dest_ptr[(i * dest_stride) + j] - overlay->shading_value;
1952 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1958 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1959 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1962 guint dest_stride, pixel_stride;
1965 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1966 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1967 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1970 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1972 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1975 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1977 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1979 for (i = y0; i < y1; i++) {
1980 for (j = x0; j < x1; j++) {
1984 y_pos = (i * dest_stride) + j * pixel_stride;
1985 y = dest_ptr[y_pos] - overlay->shading_value;
1987 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1992 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1993 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1994 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1996 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1997 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
2002 dest_ptr = dest->data[0];
2004 for (i = y0; i < y1; i++) {
2005 for (j = x0; j < x1; j++) {
2008 y_pos = (i * 4 * overlay->width) + j * 4;
2009 for (k = 0; k < 4; k++) {
2010 y = dest_ptr[y_pos + k] - overlay->shading_value;
2011 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
2019 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
2020 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2022 const int pstride = 3;
2023 gint y, x, stride, shading_val, tmp;
2026 shading_val = -overlay->shading_value;
2027 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
2029 for (y = y0; y < y1; ++y) {
2030 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
2031 p += (y * stride) + (x0 * pstride);
2032 for (x = x0; x < x1; ++x) {
2033 tmp = *p + shading_val;
2034 *p++ = CLAMP (tmp, 0, 255);
2035 tmp = *p + shading_val;
2036 *p++ = CLAMP (tmp, 0, 255);
2037 tmp = *p + shading_val;
2038 *p++ = CLAMP (tmp, 0, 255);
2044 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
2045 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2047 gint y, x, stride, shading_val, tmp;
2050 shading_val = -overlay->shading_value;
2051 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
2053 /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
2054 for (y = y0; y < y1; ++y) {
2055 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
2056 /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
2057 /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
2058 * unlikely anyone will notice.. */
2059 p += (y * stride) + ((x0 / 2) * 3) + 1;
2060 for (x = x0; x < x1; x += 2) {
2061 tmp = *p + shading_val;
2062 *p++ = CLAMP (tmp, 0, 255);
2063 tmp = *p + shading_val;
2064 *p++ = CLAMP (tmp, 0, 255);
2071 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
2072 static inline void \
2073 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
2074 gint x0, gint x1, gint y0, gint y1) \
2079 dest_ptr = dest->data[0];\
2081 for (i = y0; i < y1; i++) {\
2082 for (j = x0; j < x1; j++) {\
2084 y_pos = (i * 4 * overlay->width) + j * 4;\
2085 for (k = OFFSET; k < 3+OFFSET; k++) {\
2086 y = dest_ptr[y_pos + k] - overlay->shading_value;\
2087 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
2092 ARGB_SHADE_FUNCTION (ARGB, 1);
2093 ARGB_SHADE_FUNCTION (ABGR, 1);
2094 ARGB_SHADE_FUNCTION (RGBA, 0);
2095 ARGB_SHADE_FUNCTION (BGRA, 0);
2098 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
2099 const gchar * text, gint textlen)
2103 if (!overlay->need_render) {
2104 GST_DEBUG ("Using previously rendered text.");
2108 /* -1 is the whole string */
2109 if (text != NULL && textlen < 0) {
2110 textlen = strlen (text);
2114 string = g_strndup (text, textlen);
2115 } else { /* empty string */
2116 string = g_strdup (" ");
2118 g_strdelimit (string, "\r\t", ' ');
2119 textlen = strlen (string);
2121 /* FIXME: should we check for UTF-8 here? */
2123 GST_DEBUG ("Rendering '%s'", string);
2124 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
2128 overlay->need_render = FALSE;
2131 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
2136 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
2137 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2139 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
2140 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
2142 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
2143 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
2145 switch (overlay->format) {
2146 case GST_VIDEO_FORMAT_I420:
2147 case GST_VIDEO_FORMAT_YV12:
2148 case GST_VIDEO_FORMAT_NV12:
2149 case GST_VIDEO_FORMAT_NV21:
2150 case GST_VIDEO_FORMAT_Y41B:
2151 case GST_VIDEO_FORMAT_Y42B:
2152 case GST_VIDEO_FORMAT_Y444:
2153 case GST_VIDEO_FORMAT_YUV9:
2154 case GST_VIDEO_FORMAT_YVU9:
2155 case GST_VIDEO_FORMAT_GRAY8:
2156 case GST_VIDEO_FORMAT_A420:
2157 gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
2159 case GST_VIDEO_FORMAT_AYUV:
2160 case GST_VIDEO_FORMAT_UYVY:
2161 case GST_VIDEO_FORMAT_YUY2:
2162 case GST_VIDEO_FORMAT_v308:
2163 gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
2165 case GST_VIDEO_FORMAT_xRGB:
2166 gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
2168 case GST_VIDEO_FORMAT_xBGR:
2169 gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
2171 case GST_VIDEO_FORMAT_BGRx:
2172 gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
2174 case GST_VIDEO_FORMAT_RGBx:
2175 gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
2177 case GST_VIDEO_FORMAT_ARGB:
2178 gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
2180 case GST_VIDEO_FORMAT_ABGR:
2181 gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
2183 case GST_VIDEO_FORMAT_RGBA:
2184 gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
2186 case GST_VIDEO_FORMAT_BGRA:
2187 gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
2189 case GST_VIDEO_FORMAT_BGR:
2190 case GST_VIDEO_FORMAT_RGB:
2191 gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
2193 case GST_VIDEO_FORMAT_IYU1:
2194 gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
2197 GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
2198 gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
2203 static GstFlowReturn
2204 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
2205 GstBuffer * video_frame)
2207 GstVideoFrame frame;
2209 if (overlay->composition == NULL)
2212 if (gst_pad_check_reconfigure (overlay->srcpad))
2213 gst_base_text_overlay_negotiate (overlay, NULL);
2215 video_frame = gst_buffer_make_writable (video_frame);
2217 if (overlay->attach_compo_to_buffer) {
2218 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
2219 gst_buffer_add_video_overlay_composition_meta (video_frame,
2220 overlay->composition);
2221 /* FIXME: emulate shaded background box if want_shading=true */
2225 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
2229 /* shaded background box */
2230 if (overlay->want_shading) {
2233 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
2235 gst_base_text_overlay_shade_background (overlay, &frame,
2236 xpos, xpos + overlay->text_width, ypos, ypos + overlay->text_height);
2239 gst_video_overlay_composition_blend (overlay->composition, &frame);
2241 gst_video_frame_unmap (&frame);
2245 return gst_pad_push (overlay->srcpad, video_frame);
2250 gst_buffer_unref (video_frame);
2251 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
2256 static GstPadLinkReturn
2257 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
2260 GstBaseTextOverlay *overlay;
2262 overlay = GST_BASE_TEXT_OVERLAY (parent);
2263 if (G_UNLIKELY (!overlay))
2264 return GST_PAD_LINK_REFUSED;
2266 GST_DEBUG_OBJECT (overlay, "Text pad linked");
2268 overlay->text_linked = TRUE;
2270 return GST_PAD_LINK_OK;
2274 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
2276 GstBaseTextOverlay *overlay;
2278 /* don't use gst_pad_get_parent() here, will deadlock */
2279 overlay = GST_BASE_TEXT_OVERLAY (parent);
2281 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
2283 overlay->text_linked = FALSE;
2285 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
2289 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
2292 gboolean ret = FALSE;
2293 GstBaseTextOverlay *overlay = NULL;
2295 overlay = GST_BASE_TEXT_OVERLAY (parent);
2297 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2299 switch (GST_EVENT_TYPE (event)) {
2300 case GST_EVENT_CAPS:
2304 gst_event_parse_caps (event, &caps);
2305 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
2306 gst_event_unref (event);
2309 case GST_EVENT_SEGMENT:
2311 const GstSegment *segment;
2313 overlay->text_eos = FALSE;
2315 gst_event_parse_segment (event, &segment);
2317 if (segment->format == GST_FORMAT_TIME) {
2318 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2319 gst_segment_copy_into (segment, &overlay->text_segment);
2320 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
2321 &overlay->text_segment);
2322 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2324 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2325 ("received non-TIME newsegment event on text input"));
2328 gst_event_unref (event);
2331 /* wake up the video chain, it might be waiting for a text buffer or
2332 * a text segment update */
2333 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2334 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2335 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2340 GstClockTime start, duration;
2342 gst_event_parse_gap (event, &start, &duration);
2343 if (GST_CLOCK_TIME_IS_VALID (duration))
2345 /* we do not expect another buffer until after gap,
2346 * so that is our position now */
2347 overlay->text_segment.position = start;
2349 /* wake up the video chain, it might be waiting for a text buffer or
2350 * a text segment update */
2351 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2352 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2353 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2355 gst_event_unref (event);
2359 case GST_EVENT_FLUSH_STOP:
2360 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2361 GST_INFO_OBJECT (overlay, "text flush stop");
2362 overlay->text_flushing = FALSE;
2363 overlay->text_eos = FALSE;
2364 gst_base_text_overlay_pop_text (overlay);
2365 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2366 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2367 gst_event_unref (event);
2370 case GST_EVENT_FLUSH_START:
2371 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2372 GST_INFO_OBJECT (overlay, "text flush start");
2373 overlay->text_flushing = TRUE;
2374 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2375 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2376 gst_event_unref (event);
2380 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2381 overlay->text_eos = TRUE;
2382 GST_INFO_OBJECT (overlay, "text EOS");
2383 /* wake up the video chain, it might be waiting for a text buffer or
2384 * a text segment update */
2385 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2386 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2387 gst_event_unref (event);
2391 ret = gst_pad_event_default (pad, parent, event);
2399 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
2402 gboolean ret = FALSE;
2403 GstBaseTextOverlay *overlay = NULL;
2405 overlay = GST_BASE_TEXT_OVERLAY (parent);
2407 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2409 switch (GST_EVENT_TYPE (event)) {
2410 case GST_EVENT_CAPS:
2414 gst_event_parse_caps (event, &caps);
2415 ret = gst_base_text_overlay_setcaps (overlay, caps);
2416 gst_event_unref (event);
2419 case GST_EVENT_SEGMENT:
2421 const GstSegment *segment;
2423 GST_DEBUG_OBJECT (overlay, "received new segment");
2425 gst_event_parse_segment (event, &segment);
2427 if (segment->format == GST_FORMAT_TIME) {
2428 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
2431 gst_segment_copy_into (segment, &overlay->segment);
2433 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2434 ("received non-TIME newsegment event on video input"));
2437 ret = gst_pad_event_default (pad, parent, event);
2441 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2442 GST_INFO_OBJECT (overlay, "video EOS");
2443 overlay->video_eos = TRUE;
2444 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2445 ret = gst_pad_event_default (pad, parent, event);
2447 case GST_EVENT_FLUSH_START:
2448 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2449 GST_INFO_OBJECT (overlay, "video flush start");
2450 overlay->video_flushing = TRUE;
2451 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2452 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2453 ret = gst_pad_event_default (pad, parent, event);
2455 case GST_EVENT_FLUSH_STOP:
2456 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2457 GST_INFO_OBJECT (overlay, "video flush stop");
2458 overlay->video_flushing = FALSE;
2459 overlay->video_eos = FALSE;
2460 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2461 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2462 ret = gst_pad_event_default (pad, parent, event);
2465 ret = gst_pad_event_default (pad, parent, event);
2473 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
2476 gboolean ret = FALSE;
2477 GstBaseTextOverlay *overlay;
2479 overlay = GST_BASE_TEXT_OVERLAY (parent);
2481 switch (GST_QUERY_TYPE (query)) {
2482 case GST_QUERY_CAPS:
2484 GstCaps *filter, *caps;
2486 gst_query_parse_caps (query, &filter);
2487 caps = gst_base_text_overlay_get_videosink_caps (pad, overlay, filter);
2488 gst_query_set_caps_result (query, caps);
2489 gst_caps_unref (caps);
2494 ret = gst_pad_query_default (pad, parent, query);
2501 /* Called with lock held */
2503 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2505 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2507 if (overlay->text_buffer) {
2508 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2509 overlay->text_buffer);
2510 gst_buffer_unref (overlay->text_buffer);
2511 overlay->text_buffer = NULL;
2514 /* Let the text task know we used that buffer */
2515 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2518 /* We receive text buffers here. If they are out of segment we just ignore them.
2519 If the buffer is in our segment we keep it internally except if another one
2520 is already waiting here, in that case we wait that it gets kicked out */
2521 static GstFlowReturn
2522 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
2525 GstFlowReturn ret = GST_FLOW_OK;
2526 GstBaseTextOverlay *overlay = NULL;
2527 gboolean in_seg = FALSE;
2528 guint64 clip_start = 0, clip_stop = 0;
2530 overlay = GST_BASE_TEXT_OVERLAY (parent);
2532 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2534 if (overlay->text_flushing) {
2535 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2536 ret = GST_FLOW_FLUSHING;
2537 GST_LOG_OBJECT (overlay, "text flushing");
2541 if (overlay->text_eos) {
2542 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2544 GST_LOG_OBJECT (overlay, "text EOS");
2548 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2549 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2550 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2551 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2552 GST_BUFFER_DURATION (buffer)));
2554 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2557 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2558 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2560 stop = GST_CLOCK_TIME_NONE;
2562 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2563 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2569 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2570 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2571 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2572 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2574 /* Wait for the previous buffer to go away */
2575 while (overlay->text_buffer != NULL) {
2576 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2577 GST_DEBUG_PAD_NAME (pad));
2578 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2579 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2580 if (overlay->text_flushing) {
2581 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2582 ret = GST_FLOW_FLUSHING;
2587 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2588 overlay->text_segment.position = clip_start;
2590 overlay->text_buffer = buffer; /* pass ownership of @buffer */
2593 /* That's a new text buffer we need to render */
2594 overlay->need_render = TRUE;
2596 /* in case the video chain is waiting for a text buffer, wake it up */
2597 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2600 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2604 gst_buffer_unref (buffer);
2609 static GstFlowReturn
2610 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2613 GstBaseTextOverlayClass *klass;
2614 GstBaseTextOverlay *overlay;
2615 GstFlowReturn ret = GST_FLOW_OK;
2616 gboolean in_seg = FALSE;
2617 guint64 start, stop, clip_start = 0, clip_stop = 0;
2619 GstVideoOverlayCompositionMeta *composition_meta;
2621 overlay = GST_BASE_TEXT_OVERLAY (parent);
2623 composition_meta = gst_buffer_get_video_overlay_composition_meta (buffer);
2624 if (composition_meta) {
2625 if (overlay->upstream_composition != composition_meta->overlay) {
2626 GST_DEBUG ("GstVideoOverlayCompositionMeta found.");
2627 overlay->upstream_composition = composition_meta->overlay;
2628 overlay->need_render = TRUE;
2630 } else if (overlay->upstream_composition != NULL) {
2631 overlay->upstream_composition = NULL;
2632 overlay->need_render = TRUE;
2635 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2637 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2638 goto missing_timestamp;
2640 /* ignore buffers that are outside of the current segment */
2641 start = GST_BUFFER_TIMESTAMP (buffer);
2643 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2644 stop = GST_CLOCK_TIME_NONE;
2646 stop = start + GST_BUFFER_DURATION (buffer);
2649 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2650 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2651 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2653 /* segment_clip() will adjust start unconditionally to segment_start if
2654 * no stop time is provided, so handle this ourselves */
2655 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2656 goto out_of_segment;
2658 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2659 &clip_start, &clip_stop);
2662 goto out_of_segment;
2664 /* if the buffer is only partially in the segment, fix up stamps */
2665 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2666 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2667 buffer = gst_buffer_make_writable (buffer);
2668 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2670 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2673 /* now, after we've done the clipping, fix up end time if there's no
2674 * duration (we only use those estimated values internally though, we
2675 * don't want to set bogus values on the buffer itself) */
2677 if (overlay->info.fps_n && overlay->info.fps_d) {
2678 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2679 stop = start + gst_util_uint64_scale_int (GST_SECOND,
2680 overlay->info.fps_d, overlay->info.fps_n);
2682 GST_LOG_OBJECT (overlay, "no duration, assuming minimal duration");
2683 stop = start + 1; /* we need to assume some interval */
2687 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2691 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2693 if (overlay->video_flushing)
2696 if (overlay->video_eos)
2699 if (overlay->silent) {
2700 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2701 ret = gst_pad_push (overlay->srcpad, buffer);
2703 /* Update position */
2704 overlay->segment.position = clip_start;
2709 /* Text pad not linked, rendering internal text */
2710 if (!overlay->text_linked) {
2711 if (klass->get_text) {
2712 text = klass->get_text (overlay, buffer);
2714 text = g_strdup (overlay->default_text);
2717 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2718 "text: '%s'", GST_STR_NULL (text));
2720 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2722 if (text != NULL && *text != '\0') {
2723 /* Render and push */
2724 gst_base_text_overlay_render_text (overlay, text, -1);
2725 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2727 /* Invalid or empty string */
2728 ret = gst_pad_push (overlay->srcpad, buffer);
2731 /* Text pad linked, check if we have a text buffer queued */
2732 if (overlay->text_buffer) {
2733 gboolean pop_text = FALSE, valid_text_time = TRUE;
2734 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2735 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2736 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2737 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2738 GstClockTime vid_running_time, vid_running_time_end;
2740 /* if the text buffer isn't stamped right, pop it off the
2741 * queue and display it for the current video frame only */
2742 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2743 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2744 GST_WARNING_OBJECT (overlay,
2745 "Got text buffer with invalid timestamp or duration");
2747 valid_text_time = FALSE;
2749 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2750 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2754 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2756 vid_running_time_end =
2757 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2760 /* If timestamp and duration are valid */
2761 if (valid_text_time) {
2763 gst_segment_to_running_time (&overlay->text_segment,
2764 GST_FORMAT_TIME, text_start);
2765 text_running_time_end =
2766 gst_segment_to_running_time (&overlay->text_segment,
2767 GST_FORMAT_TIME, text_end);
2770 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2771 GST_TIME_ARGS (text_running_time),
2772 GST_TIME_ARGS (text_running_time_end));
2773 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2774 GST_TIME_ARGS (vid_running_time),
2775 GST_TIME_ARGS (vid_running_time_end));
2777 /* Text too old or in the future */
2778 if (valid_text_time && text_running_time_end <= vid_running_time) {
2779 /* text buffer too old, get rid of it and do nothing */
2780 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2782 gst_base_text_overlay_pop_text (overlay);
2783 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2784 goto wait_for_text_buf;
2785 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2786 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2787 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2788 /* Push the video frame */
2789 ret = gst_pad_push (overlay->srcpad, buffer);
2795 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2796 in_text = (gchar *) map.data;
2800 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2801 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2802 * here on purpose, this is something that needs fixing upstream */
2803 if (!g_utf8_validate (in_text, in_size, NULL)) {
2804 const gchar *end = NULL;
2806 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2807 in_text = g_strndup (in_text, in_size);
2808 while (!g_utf8_validate (in_text, in_size, &end) && end)
2809 *((gchar *) end) = '*';
2812 /* Get the string */
2813 if (overlay->have_pango_markup) {
2814 text = g_strndup (in_text, in_size);
2816 text = g_markup_escape_text (in_text, in_size);
2819 if (text != NULL && *text != '\0') {
2820 gint text_len = strlen (text);
2822 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2823 text[text_len - 1] == '\r')) {
2826 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2827 gst_base_text_overlay_render_text (overlay, text, text_len);
2829 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2830 gst_base_text_overlay_render_text (overlay, " ", 1);
2832 if (in_text != (gchar *) map.data)
2835 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2836 gst_base_text_overlay_render_text (overlay, " ", 1);
2839 gst_buffer_unmap (overlay->text_buffer, &map);
2841 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2842 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2844 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2845 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2850 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2851 gst_base_text_overlay_pop_text (overlay);
2852 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2855 gboolean wait_for_text_buf = TRUE;
2857 if (overlay->text_eos)
2858 wait_for_text_buf = FALSE;
2860 if (!overlay->wait_text)
2861 wait_for_text_buf = FALSE;
2863 /* Text pad linked, but no text buffer available - what now? */
2864 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2865 GstClockTime text_start_running_time, text_position_running_time;
2866 GstClockTime vid_running_time;
2869 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2870 GST_BUFFER_TIMESTAMP (buffer));
2871 text_start_running_time =
2872 gst_segment_to_running_time (&overlay->text_segment,
2873 GST_FORMAT_TIME, overlay->text_segment.start);
2874 text_position_running_time =
2875 gst_segment_to_running_time (&overlay->text_segment,
2876 GST_FORMAT_TIME, overlay->text_segment.position);
2878 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2879 vid_running_time < text_start_running_time) ||
2880 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2881 vid_running_time < text_position_running_time)) {
2882 wait_for_text_buf = FALSE;
2886 if (wait_for_text_buf) {
2887 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2888 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2889 GST_DEBUG_OBJECT (overlay, "resuming");
2890 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2891 goto wait_for_text_buf;
2893 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2894 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2895 ret = gst_pad_push (overlay->srcpad, buffer);
2902 /* Update position */
2903 overlay->segment.position = clip_start;
2909 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2910 gst_buffer_unref (buffer);
2916 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2917 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2918 gst_buffer_unref (buffer);
2919 return GST_FLOW_FLUSHING;
2923 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2924 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2925 gst_buffer_unref (buffer);
2926 return GST_FLOW_EOS;
2930 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2931 gst_buffer_unref (buffer);
2936 static GstStateChangeReturn
2937 gst_base_text_overlay_change_state (GstElement * element,
2938 GstStateChange transition)
2940 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2941 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2943 switch (transition) {
2944 case GST_STATE_CHANGE_PAUSED_TO_READY:
2945 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2946 overlay->text_flushing = TRUE;
2947 overlay->video_flushing = TRUE;
2948 /* pop_text will broadcast on the GCond and thus also make the video
2949 * chain exit if it's waiting for a text buffer */
2950 gst_base_text_overlay_pop_text (overlay);
2951 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2957 ret = parent_class->change_state (element, transition);
2958 if (ret == GST_STATE_CHANGE_FAILURE)
2961 switch (transition) {
2962 case GST_STATE_CHANGE_READY_TO_PAUSED:
2963 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2964 overlay->text_flushing = FALSE;
2965 overlay->video_flushing = FALSE;
2966 overlay->video_eos = FALSE;
2967 overlay->text_eos = FALSE;
2968 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2969 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2970 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2980 plugin_init (GstPlugin * plugin)
2982 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2983 GST_TYPE_TEXT_OVERLAY) ||
2984 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2985 GST_TYPE_TIME_OVERLAY) ||
2986 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2987 GST_TYPE_CLOCK_OVERLAY) ||
2988 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2989 GST_TYPE_TEXT_RENDER)) {
2993 /*texttestsrc_plugin_init(module, plugin); */
2995 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
3000 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
3001 pango, "Pango-based text rendering and overlay", plugin_init,
3002 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)