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.
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
142 PROP_AUTO_ADJUST_SIZE,
143 PROP_VERTICAL_RENDER,
150 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
152 static GstStaticPadTemplate src_template_factory =
153 GST_STATIC_PAD_TEMPLATE ("src",
156 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
159 static GstStaticPadTemplate video_sink_template_factory =
160 GST_STATIC_PAD_TEMPLATE ("video_sink",
163 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
166 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
168 gst_base_text_overlay_valign_get_type (void)
170 static GType base_text_overlay_valign_type = 0;
171 static const GEnumValue base_text_overlay_valign[] = {
172 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
173 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
174 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
175 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
176 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
180 if (!base_text_overlay_valign_type) {
181 base_text_overlay_valign_type =
182 g_enum_register_static ("GstBaseTextOverlayVAlign",
183 base_text_overlay_valign);
185 return base_text_overlay_valign_type;
188 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
190 gst_base_text_overlay_halign_get_type (void)
192 static GType base_text_overlay_halign_type = 0;
193 static const GEnumValue base_text_overlay_halign[] = {
194 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
195 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
196 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
197 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
201 if (!base_text_overlay_halign_type) {
202 base_text_overlay_halign_type =
203 g_enum_register_static ("GstBaseTextOverlayHAlign",
204 base_text_overlay_halign);
206 return base_text_overlay_halign_type;
210 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
212 gst_base_text_overlay_wrap_mode_get_type (void)
214 static GType base_text_overlay_wrap_mode_type = 0;
215 static const GEnumValue base_text_overlay_wrap_mode[] = {
216 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
217 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
218 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
219 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
223 if (!base_text_overlay_wrap_mode_type) {
224 base_text_overlay_wrap_mode_type =
225 g_enum_register_static ("GstBaseTextOverlayWrapMode",
226 base_text_overlay_wrap_mode);
228 return base_text_overlay_wrap_mode_type;
231 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
233 gst_base_text_overlay_line_align_get_type (void)
235 static GType base_text_overlay_line_align_type = 0;
236 static const GEnumValue base_text_overlay_line_align[] = {
237 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
238 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
239 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
243 if (!base_text_overlay_line_align_type) {
244 base_text_overlay_line_align_type =
245 g_enum_register_static ("GstBaseTextOverlayLineAlign",
246 base_text_overlay_line_align);
248 return base_text_overlay_line_align_type;
251 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
252 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
253 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
254 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
255 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
256 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
257 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
259 static GstElementClass *parent_class = NULL;
260 static void gst_base_text_overlay_base_init (gpointer g_class);
261 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
262 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
263 GstBaseTextOverlayClass * klass);
265 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
266 element, GstStateChange transition);
268 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
269 GstBaseTextOverlay * overlay, GstCaps * filter);
270 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
272 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
274 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
275 GstObject * parent, GstEvent * event);
276 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
277 GstObject * parent, GstQuery * query);
279 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
280 GstObject * parent, GstEvent * event);
281 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
282 GstObject * parent, GstQuery * query);
283 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
284 GstObject * parent, GstBuffer * buffer);
286 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
287 GstObject * parent, GstEvent * event);
288 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
289 GstObject * parent, GstBuffer * buffer);
290 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
291 GstObject * parent, GstPad * peer);
292 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
294 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
295 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
298 static void gst_base_text_overlay_finalize (GObject * object);
299 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
300 const GValue * value, GParamSpec * pspec);
301 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
302 GValue * value, GParamSpec * pspec);
304 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
305 PangoFontDescription * desc);
308 gst_base_text_overlay_get_type (void)
310 static GType type = 0;
312 if (g_once_init_enter ((gsize *) & type)) {
313 static const GTypeInfo info = {
314 sizeof (GstBaseTextOverlayClass),
315 (GBaseInitFunc) gst_base_text_overlay_base_init,
317 (GClassInitFunc) gst_base_text_overlay_class_init,
320 sizeof (GstBaseTextOverlay),
322 (GInstanceInitFunc) gst_base_text_overlay_init,
325 g_once_init_leave ((gsize *) & type,
326 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
334 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
335 GstBuffer * video_frame)
337 return g_strdup (overlay->default_text);
341 gst_base_text_overlay_base_init (gpointer g_class)
343 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
344 PangoFontMap *fontmap;
346 /* Only lock for the subclasses here, the base class
347 * doesn't have this mutex yet and it's not necessary
349 if (klass->pango_lock)
350 g_mutex_lock (klass->pango_lock);
351 fontmap = pango_cairo_font_map_get_default ();
352 klass->pango_context =
353 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
354 if (klass->pango_lock)
355 g_mutex_unlock (klass->pango_lock);
359 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
361 GObjectClass *gobject_class;
362 GstElementClass *gstelement_class;
364 gobject_class = (GObjectClass *) klass;
365 gstelement_class = (GstElementClass *) klass;
367 parent_class = g_type_class_peek_parent (klass);
369 gobject_class->finalize = gst_base_text_overlay_finalize;
370 gobject_class->set_property = gst_base_text_overlay_set_property;
371 gobject_class->get_property = gst_base_text_overlay_get_property;
373 gst_element_class_add_pad_template (gstelement_class,
374 gst_static_pad_template_get (&src_template_factory));
375 gst_element_class_add_pad_template (gstelement_class,
376 gst_static_pad_template_get (&video_sink_template_factory));
378 gstelement_class->change_state =
379 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
381 klass->pango_lock = g_slice_new (GMutex);
382 g_mutex_init (klass->pango_lock);
384 klass->get_text = gst_base_text_overlay_get_text;
386 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
387 g_param_spec_string ("text", "text",
388 "Text to be display.", DEFAULT_PROP_TEXT,
389 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
390 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
391 g_param_spec_boolean ("shaded-background", "shaded background",
392 "Whether to shade the background under the text area",
393 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
394 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
395 g_param_spec_enum ("valignment", "vertical alignment",
396 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
397 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
398 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
399 g_param_spec_enum ("halignment", "horizontal alignment",
400 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
401 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
402 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
403 g_param_spec_int ("xpad", "horizontal paddding",
404 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
405 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
406 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
407 g_param_spec_int ("ypad", "vertical padding",
408 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
409 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
410 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
411 g_param_spec_int ("deltax", "X position modifier",
412 "Shift X position to the left or to the right. Unit is pixels.",
413 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
414 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
415 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
416 g_param_spec_int ("deltay", "Y position modifier",
417 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
418 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
420 * GstBaseTextOverlay:xpos
422 * Horizontal position of the rendered text when using positioned alignment.
426 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
427 g_param_spec_double ("xpos", "horizontal position",
428 "Horizontal position when using position alignment", 0, 1.0,
430 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
432 * GstBaseTextOverlay:ypos
434 * Vertical position of the rendered text when using positioned alignment.
438 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
439 g_param_spec_double ("ypos", "vertical position",
440 "Vertical position when using position alignment", 0, 1.0,
442 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
443 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
444 g_param_spec_enum ("wrap-mode", "wrap mode",
445 "Whether to wrap the text and if so how.",
446 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
447 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
448 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
449 g_param_spec_string ("font-desc", "font description",
450 "Pango font description of font to be used for rendering. "
451 "See documentation of pango_font_description_from_string "
452 "for syntax.", DEFAULT_PROP_FONT_DESC,
453 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
455 * GstBaseTextOverlay:color
457 * Color of the rendered text.
461 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
462 g_param_spec_uint ("color", "Color",
463 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
465 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
467 * GstTextOverlay:outline-color
469 * Color of the outline of the rendered text.
473 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
474 g_param_spec_uint ("outline-color", "Text Outline Color",
475 "Color to use for outline the text (big-endian ARGB).", 0,
476 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
477 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
480 * GstBaseTextOverlay:line-alignment
482 * Alignment of text lines relative to each other (for multi-line text)
486 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
487 g_param_spec_enum ("line-alignment", "line alignment",
488 "Alignment of text lines relative to each other.",
489 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
490 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
492 * GstBaseTextOverlay:silent
494 * If set, no text is rendered. Useful to switch off text rendering
495 * temporarily without removing the textoverlay element from the pipeline.
499 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
500 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
501 g_param_spec_boolean ("silent", "silent",
502 "Whether to render the text string",
504 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
506 * GstBaseTextOverlay:wait-text
508 * If set, the video will block until a subtitle is received on the text pad.
509 * If video and subtitles are sent in sync, like from the same demuxer, this
510 * property should be set.
514 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
515 g_param_spec_boolean ("wait-text", "Wait Text",
516 "Whether to wait for subtitles",
517 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
519 g_object_class_install_property (G_OBJECT_CLASS (klass),
520 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
521 "Automatically adjust font size to screen-size.",
522 DEFAULT_PROP_AUTO_ADJUST_SIZE,
523 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
525 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
526 g_param_spec_boolean ("vertical-render", "vertical render",
527 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
528 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
532 gst_base_text_overlay_finalize (GObject * object)
534 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
536 g_free (overlay->default_text);
538 if (overlay->composition) {
539 gst_video_overlay_composition_unref (overlay->composition);
540 overlay->composition = NULL;
543 if (overlay->text_image) {
544 gst_buffer_unref (overlay->text_image);
545 overlay->text_image = NULL;
548 if (overlay->layout) {
549 g_object_unref (overlay->layout);
550 overlay->layout = NULL;
553 if (overlay->text_buffer) {
554 gst_buffer_unref (overlay->text_buffer);
555 overlay->text_buffer = NULL;
558 g_mutex_clear (&overlay->lock);
559 g_cond_clear (&overlay->cond);
561 G_OBJECT_CLASS (parent_class)->finalize (object);
565 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
566 GstBaseTextOverlayClass * klass)
568 GstPadTemplate *template;
569 PangoFontDescription *desc;
572 template = gst_static_pad_template_get (&video_sink_template_factory);
573 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
574 gst_object_unref (template);
575 gst_pad_set_event_function (overlay->video_sinkpad,
576 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
577 gst_pad_set_chain_function (overlay->video_sinkpad,
578 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
579 gst_pad_set_query_function (overlay->video_sinkpad,
580 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
581 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
582 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
585 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
589 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
591 gst_pad_set_event_function (overlay->text_sinkpad,
592 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
593 gst_pad_set_chain_function (overlay->text_sinkpad,
594 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
595 gst_pad_set_link_function (overlay->text_sinkpad,
596 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
597 gst_pad_set_unlink_function (overlay->text_sinkpad,
598 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
599 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
603 template = gst_static_pad_template_get (&src_template_factory);
604 overlay->srcpad = gst_pad_new_from_template (template, "src");
605 gst_object_unref (template);
606 gst_pad_set_event_function (overlay->srcpad,
607 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
608 gst_pad_set_query_function (overlay->srcpad,
609 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
610 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
612 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
613 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
615 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
616 (overlay)->pango_context);
618 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
619 (overlay)->pango_context);
620 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
622 overlay->color = DEFAULT_PROP_COLOR;
623 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
624 overlay->halign = DEFAULT_PROP_HALIGNMENT;
625 overlay->valign = DEFAULT_PROP_VALIGNMENT;
626 overlay->xpad = DEFAULT_PROP_XPAD;
627 overlay->ypad = DEFAULT_PROP_YPAD;
628 overlay->deltax = DEFAULT_PROP_DELTAX;
629 overlay->deltay = DEFAULT_PROP_DELTAY;
630 overlay->xpos = DEFAULT_PROP_XPOS;
631 overlay->ypos = DEFAULT_PROP_YPOS;
633 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
635 overlay->want_shading = DEFAULT_PROP_SHADING;
636 overlay->shading_value = DEFAULT_SHADING_VALUE;
637 overlay->silent = DEFAULT_PROP_SILENT;
638 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
639 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
641 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
642 overlay->need_render = TRUE;
643 overlay->text_image = NULL;
644 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
645 gst_base_text_overlay_update_render_mode (overlay);
647 overlay->text_buffer = NULL;
648 overlay->text_linked = FALSE;
649 g_mutex_init (&overlay->lock);
650 g_cond_init (&overlay->cond);
651 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
652 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
656 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
658 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
659 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
660 pango_layout_set_width (overlay->layout, -1);
664 if (overlay->auto_adjust_size) {
665 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
666 if (overlay->use_vertical_render) {
667 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
671 (overlay->use_vertical_render ? overlay->height : overlay->width) *
675 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
676 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
677 pango_layout_set_width (overlay->layout, width);
678 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
683 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
685 PangoMatrix matrix = PANGO_MATRIX_INIT;
686 PangoContext *context = pango_layout_get_context (overlay->layout);
688 if (overlay->use_vertical_render) {
689 pango_matrix_rotate (&matrix, -90);
690 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
691 pango_context_set_matrix (context, &matrix);
692 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
694 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
695 pango_context_set_matrix (context, &matrix);
696 pango_layout_set_alignment (overlay->layout,
697 (PangoAlignment) overlay->line_align);
702 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
704 GstStructure *structure;
707 structure = gst_caps_get_structure (caps, 0);
708 format = gst_structure_get_string (structure, "format");
709 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
714 /* FIXME: upstream nego (e.g. when the video window is resized) */
716 /* only negotiate/query video overlay composition support for now */
718 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
722 gboolean attach = FALSE;
724 GST_DEBUG_OBJECT (overlay, "performing negotiation");
726 target = gst_pad_get_current_caps (overlay->srcpad);
728 if (!target || gst_caps_is_empty (target))
731 /* find supported meta */
732 query = gst_query_new_allocation (target, TRUE);
734 if (!gst_pad_peer_query (overlay->srcpad, query)) {
735 /* no problem, we use the query defaults */
736 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
739 if (gst_query_find_allocation_meta (query,
740 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
743 overlay->attach_compo_to_buffer = attach;
745 gst_query_unref (query);
746 gst_caps_unref (target);
753 gst_caps_unref (target);
759 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
762 gboolean ret = FALSE;
764 if (!gst_video_info_from_caps (&info, caps))
767 overlay->info = info;
768 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
769 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
770 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
772 ret = gst_pad_set_caps (overlay->srcpad, caps);
775 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
776 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
777 gst_base_text_overlay_negotiate (overlay);
778 gst_base_text_overlay_update_wrap_mode (overlay);
779 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
780 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
788 GST_DEBUG_OBJECT (overlay, "could not parse caps");
794 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
795 const GValue * value, GParamSpec * pspec)
797 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
799 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
802 g_free (overlay->default_text);
803 overlay->default_text = g_value_dup_string (value);
804 overlay->need_render = TRUE;
807 overlay->want_shading = g_value_get_boolean (value);
810 overlay->xpad = g_value_get_int (value);
813 overlay->ypad = g_value_get_int (value);
816 overlay->deltax = g_value_get_int (value);
819 overlay->deltay = g_value_get_int (value);
822 overlay->xpos = g_value_get_double (value);
825 overlay->ypos = g_value_get_double (value);
827 case PROP_VALIGNMENT:
828 overlay->valign = g_value_get_enum (value);
830 case PROP_HALIGNMENT:
831 overlay->halign = g_value_get_enum (value);
834 overlay->wrap_mode = g_value_get_enum (value);
835 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
836 gst_base_text_overlay_update_wrap_mode (overlay);
837 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
841 PangoFontDescription *desc;
842 const gchar *fontdesc_str;
844 fontdesc_str = g_value_get_string (value);
845 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
846 desc = pango_font_description_from_string (fontdesc_str);
848 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
849 pango_layout_set_font_description (overlay->layout, desc);
850 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
851 pango_font_description_free (desc);
853 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
856 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
860 overlay->color = g_value_get_uint (value);
862 case PROP_OUTLINE_COLOR:
863 overlay->outline_color = g_value_get_uint (value);
866 overlay->silent = g_value_get_boolean (value);
868 case PROP_LINE_ALIGNMENT:
869 overlay->line_align = g_value_get_enum (value);
870 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
871 pango_layout_set_alignment (overlay->layout,
872 (PangoAlignment) overlay->line_align);
873 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
876 overlay->wait_text = g_value_get_boolean (value);
878 case PROP_AUTO_ADJUST_SIZE:
879 overlay->auto_adjust_size = g_value_get_boolean (value);
880 overlay->need_render = TRUE;
882 case PROP_VERTICAL_RENDER:
883 overlay->use_vertical_render = g_value_get_boolean (value);
884 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
885 gst_base_text_overlay_update_render_mode (overlay);
886 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
887 overlay->need_render = TRUE;
890 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
894 overlay->need_render = TRUE;
895 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
899 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
900 GValue * value, GParamSpec * pspec)
902 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
904 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
907 g_value_set_string (value, overlay->default_text);
910 g_value_set_boolean (value, overlay->want_shading);
913 g_value_set_int (value, overlay->xpad);
916 g_value_set_int (value, overlay->ypad);
919 g_value_set_int (value, overlay->deltax);
922 g_value_set_int (value, overlay->deltay);
925 g_value_set_double (value, overlay->xpos);
928 g_value_set_double (value, overlay->ypos);
930 case PROP_VALIGNMENT:
931 g_value_set_enum (value, overlay->valign);
933 case PROP_HALIGNMENT:
934 g_value_set_enum (value, overlay->halign);
937 g_value_set_enum (value, overlay->wrap_mode);
940 g_value_set_boolean (value, overlay->silent);
942 case PROP_LINE_ALIGNMENT:
943 g_value_set_enum (value, overlay->line_align);
946 g_value_set_boolean (value, overlay->wait_text);
948 case PROP_AUTO_ADJUST_SIZE:
949 g_value_set_boolean (value, overlay->auto_adjust_size);
951 case PROP_VERTICAL_RENDER:
952 g_value_set_boolean (value, overlay->use_vertical_render);
955 g_value_set_uint (value, overlay->color);
957 case PROP_OUTLINE_COLOR:
958 g_value_set_uint (value, overlay->outline_color);
961 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
965 overlay->need_render = TRUE;
966 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
970 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
973 gboolean ret = FALSE;
974 GstBaseTextOverlay *overlay;
976 overlay = GST_BASE_TEXT_OVERLAY (parent);
978 switch (GST_QUERY_TYPE (query)) {
981 GstCaps *filter, *caps;
983 gst_query_parse_caps (query, &filter);
984 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
985 gst_query_set_caps_result (query, caps);
986 gst_caps_unref (caps);
991 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
999 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1002 gboolean ret = FALSE;
1003 GstBaseTextOverlay *overlay = NULL;
1005 overlay = GST_BASE_TEXT_OVERLAY (parent);
1007 switch (GST_EVENT_TYPE (event)) {
1008 case GST_EVENT_SEEK:{
1011 /* We don't handle seek if we have not text pad */
1012 if (!overlay->text_linked) {
1013 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1014 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1018 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1020 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1022 /* Flush downstream, only for flushing seek */
1023 if (flags & GST_SEEK_FLAG_FLUSH)
1024 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1026 /* Mark ourself as flushing, unblock chains */
1027 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1028 overlay->video_flushing = TRUE;
1029 overlay->text_flushing = TRUE;
1030 gst_base_text_overlay_pop_text (overlay);
1031 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1033 /* Seek on each sink pad */
1034 gst_event_ref (event);
1035 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1037 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1039 gst_event_unref (event);
1044 if (overlay->text_linked) {
1045 gst_event_ref (event);
1046 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1047 gst_pad_push_event (overlay->text_sinkpad, event);
1049 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1060 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1066 if (G_UNLIKELY (!overlay))
1067 return gst_pad_get_pad_template_caps (pad);
1069 if (pad == overlay->srcpad)
1070 otherpad = overlay->video_sinkpad;
1072 otherpad = overlay->srcpad;
1074 /* we can do what the peer can */
1075 caps = gst_pad_peer_query_caps (otherpad, filter);
1077 GstCaps *temp, *templ;
1079 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1081 /* filtered against our padtemplate */
1082 templ = gst_pad_get_pad_template_caps (otherpad);
1083 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1084 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1085 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1086 gst_caps_unref (caps);
1087 gst_caps_unref (templ);
1088 /* this is what we can do */
1091 /* no peer, our padtemplate is enough then */
1092 caps = gst_pad_get_pad_template_caps (pad);
1094 GstCaps *intersection;
1097 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1098 gst_caps_unref (caps);
1099 caps = intersection;
1103 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1109 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1110 PangoFontDescription * desc)
1112 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1113 overlay->shadow_offset = (double) (font_size) / 13.0;
1114 overlay->outline_offset = (double) (font_size) / 15.0;
1115 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1116 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1120 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1121 gint * xpos, gint * ypos)
1124 GstBaseTextOverlayVAlign valign;
1125 GstBaseTextOverlayHAlign halign;
1127 width = overlay->image_width;
1128 height = overlay->image_height;
1130 if (overlay->use_vertical_render)
1131 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1133 halign = overlay->halign;
1136 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1137 *xpos = overlay->xpad;
1139 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1140 *xpos = (overlay->width - width) / 2;
1142 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1143 *xpos = overlay->width - width - overlay->xpad;
1145 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1146 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1147 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1154 *xpos += overlay->deltax;
1156 if (overlay->use_vertical_render)
1157 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1159 valign = overlay->valign;
1162 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1163 *ypos = overlay->height - height - overlay->ypad;
1165 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1166 *ypos = overlay->height - (height + overlay->ypad);
1168 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1169 *ypos = overlay->ypad;
1171 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1172 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1173 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1175 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1176 *ypos = (overlay->height - height) / 2;
1179 *ypos = overlay->ypad;
1182 *ypos += overlay->deltay;
1186 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1189 GstVideoOverlayRectangle *rectangle;
1191 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1193 if (overlay->text_image) {
1194 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1195 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1196 overlay->image_width, overlay->image_height);
1197 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1198 xpos, ypos, overlay->image_width, overlay->image_height,
1199 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1201 if (overlay->composition)
1202 gst_video_overlay_composition_unref (overlay->composition);
1203 overlay->composition = gst_video_overlay_composition_new (rectangle);
1204 gst_video_overlay_rectangle_unref (rectangle);
1206 } else if (overlay->composition) {
1207 gst_video_overlay_composition_unref (overlay->composition);
1208 overlay->composition = NULL;
1213 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1215 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1223 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1224 const gchar * string, gint textlen)
1227 cairo_surface_t *surface;
1228 PangoRectangle ink_rect, logical_rect;
1229 cairo_matrix_t cairo_matrix;
1231 double scalef = 1.0;
1236 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1238 if (overlay->auto_adjust_size) {
1239 /* 640 pixel is default */
1240 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1242 pango_layout_set_width (overlay->layout, -1);
1243 /* set text on pango layout */
1244 pango_layout_set_markup (overlay->layout, string, textlen);
1246 /* get subtitle image size */
1247 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1249 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1251 if (width + overlay->deltax >
1252 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1254 * subtitle image width is larger then overlay width
1255 * so rearrange overlay wrap mode.
1257 gst_base_text_overlay_update_wrap_mode (overlay);
1258 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1259 width = overlay->width;
1263 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1264 if (height > overlay->height) {
1265 height = overlay->height;
1267 if (overlay->use_vertical_render) {
1268 PangoRectangle rect;
1269 PangoContext *context;
1270 PangoMatrix matrix = PANGO_MATRIX_INIT;
1273 context = pango_layout_get_context (overlay->layout);
1275 pango_matrix_rotate (&matrix, -90);
1277 rect.x = rect.y = 0;
1279 rect.height = height;
1280 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1281 matrix.x0 = -rect.x;
1282 matrix.y0 = -rect.y;
1284 pango_context_set_matrix (context, &matrix);
1286 cairo_matrix.xx = matrix.xx;
1287 cairo_matrix.yx = matrix.yx;
1288 cairo_matrix.xy = matrix.xy;
1289 cairo_matrix.yy = matrix.yy;
1290 cairo_matrix.x0 = matrix.x0;
1291 cairo_matrix.y0 = matrix.y0;
1292 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1298 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1301 /* reallocate overlay buffer */
1302 buffer = gst_buffer_new_and_alloc (4 * width * height);
1303 gst_buffer_replace (&overlay->text_image, buffer);
1304 gst_buffer_unref (buffer);
1306 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1307 surface = cairo_image_surface_create_for_data (map.data,
1308 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1309 cr = cairo_create (surface);
1312 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1315 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1317 if (overlay->want_shading)
1318 cairo_paint_with_alpha (cr, overlay->shading_value);
1320 /* apply transformations */
1321 cairo_set_matrix (cr, &cairo_matrix);
1323 /* FIXME: We use show_layout everywhere except for the surface
1324 * because it's really faster and internally does all kinds of
1325 * caching. Unfortunately we have to paint to a cairo path for
1326 * the outline and this is slow. Once Pango supports user fonts
1327 * we should use them, see
1328 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1330 * Idea would the be, to create a cairo user font that
1331 * does shadow, outline, text painting in the
1332 * render_glyph function.
1335 /* draw shadow text */
1337 PangoAttrList *origin_attr, *filtered_attr, *temp_attr;
1339 /* Store a ref on the original attributes for later restoration */
1341 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1342 /* Take a copy of the original attributes, because pango_attr_list_filter
1343 * modifies the passed list */
1344 temp_attr = pango_attr_list_copy (origin_attr);
1346 pango_attr_list_filter (temp_attr,
1347 gst_text_overlay_filter_foreground_attr, NULL);
1348 pango_attr_list_unref (temp_attr);
1351 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1352 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1353 pango_layout_set_attributes (overlay->layout, filtered_attr);
1354 pango_cairo_show_layout (cr, overlay->layout);
1355 pango_layout_set_attributes (overlay->layout, origin_attr);
1356 pango_attr_list_unref (filtered_attr);
1357 pango_attr_list_unref (origin_attr);
1361 a = (overlay->outline_color >> 24) & 0xff;
1362 r = (overlay->outline_color >> 16) & 0xff;
1363 g = (overlay->outline_color >> 8) & 0xff;
1364 b = (overlay->outline_color >> 0) & 0xff;
1366 /* draw outline text */
1368 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1369 cairo_set_line_width (cr, overlay->outline_offset);
1370 pango_cairo_layout_path (cr, overlay->layout);
1374 a = (overlay->color >> 24) & 0xff;
1375 r = (overlay->color >> 16) & 0xff;
1376 g = (overlay->color >> 8) & 0xff;
1377 b = (overlay->color >> 0) & 0xff;
1381 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1382 pango_cairo_show_layout (cr, overlay->layout);
1386 cairo_surface_destroy (surface);
1387 gst_buffer_unmap (buffer, &map);
1388 overlay->image_width = width;
1389 overlay->image_height = height;
1390 overlay->baseline_y = ink_rect.y;
1391 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1393 gst_base_text_overlay_set_composition (overlay);
1397 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1398 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1400 gint i, j, dest_stride;
1403 dest_stride = dest->info.stride[0];
1404 dest_ptr = dest->data[0];
1406 for (i = y0; i < y1; ++i) {
1407 for (j = x0; j < x1; ++j) {
1408 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1410 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1416 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1417 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1420 guint dest_stride, pixel_stride;
1423 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1424 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1425 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1428 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1430 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1433 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1435 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1437 for (i = y0; i < y1; i++) {
1438 for (j = x0; j < x1; j++) {
1442 y_pos = (i * dest_stride) + j * pixel_stride;
1443 y = dest_ptr[y_pos] + overlay->shading_value;
1445 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1450 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1451 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1452 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1454 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1455 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1460 dest_ptr = dest->data[0];
1462 for (i = y0; i < y1; i++) {
1463 for (j = x0; j < x1; j++) {
1466 y_pos = (i * 4 * overlay->width) + j * 4;
1467 for (k = 0; k < 4; k++) {
1468 y = dest_ptr[y_pos + k] + overlay->shading_value;
1469 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1477 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
1478 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1480 const int pstride = 3;
1481 gint y, x, stride, shading_val, tmp;
1484 shading_val = overlay->shading_value;
1485 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1487 for (y = y0; y < y1; ++y) {
1488 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1489 p += (y * stride) + (x0 * pstride);
1490 for (x = x0; x < x1; ++x) {
1491 tmp = *p + shading_val;
1492 *p++ = CLAMP (tmp, 0, 255);
1493 tmp = *p + shading_val;
1494 *p++ = CLAMP (tmp, 0, 255);
1495 tmp = *p + shading_val;
1496 *p++ = CLAMP (tmp, 0, 255);
1502 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
1503 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1505 gint y, x, stride, shading_val, tmp;
1508 shading_val = overlay->shading_value;
1509 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1511 /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
1512 for (y = y0; y < y1; ++y) {
1513 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1514 /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
1515 /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
1516 * unlikely anyone will notice.. */
1517 p += (y * stride) + ((x0 / 2) * 3) + 1;
1518 for (x = x0; x < x1; x += 2) {
1519 tmp = *p + shading_val;
1520 *p++ = CLAMP (tmp, 0, 255);
1521 tmp = *p + shading_val;
1522 *p++ = CLAMP (tmp, 0, 255);
1529 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1530 static inline void \
1531 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1532 gint x0, gint x1, gint y0, gint y1) \
1537 dest_ptr = dest->data[0];\
1539 for (i = y0; i < y1; i++) {\
1540 for (j = x0; j < x1; j++) {\
1542 y_pos = (i * 4 * overlay->width) + j * 4;\
1543 for (k = OFFSET; k < 3+OFFSET; k++) {\
1544 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1545 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1550 ARGB_SHADE_FUNCTION (ARGB, 1);
1551 ARGB_SHADE_FUNCTION (ABGR, 1);
1552 ARGB_SHADE_FUNCTION (RGBA, 0);
1553 ARGB_SHADE_FUNCTION (BGRA, 0);
1556 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1557 const gchar * text, gint textlen)
1561 if (!overlay->need_render) {
1562 GST_DEBUG ("Using previously rendered text.");
1566 /* -1 is the whole string */
1567 if (text != NULL && textlen < 0) {
1568 textlen = strlen (text);
1572 string = g_strndup (text, textlen);
1573 } else { /* empty string */
1574 string = g_strdup (" ");
1576 g_strdelimit (string, "\r\t", ' ');
1577 textlen = strlen (string);
1579 /* FIXME: should we check for UTF-8 here? */
1581 GST_DEBUG ("Rendering '%s'", string);
1582 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1586 overlay->need_render = FALSE;
1589 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
1594 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
1595 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1597 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1598 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1600 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1601 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1603 switch (overlay->format) {
1604 case GST_VIDEO_FORMAT_I420:
1605 case GST_VIDEO_FORMAT_YV12:
1606 case GST_VIDEO_FORMAT_NV12:
1607 case GST_VIDEO_FORMAT_NV21:
1608 case GST_VIDEO_FORMAT_Y41B:
1609 case GST_VIDEO_FORMAT_Y42B:
1610 case GST_VIDEO_FORMAT_Y444:
1611 case GST_VIDEO_FORMAT_YUV9:
1612 case GST_VIDEO_FORMAT_YVU9:
1613 case GST_VIDEO_FORMAT_GRAY8:
1614 case GST_VIDEO_FORMAT_A420:
1615 gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
1617 case GST_VIDEO_FORMAT_AYUV:
1618 case GST_VIDEO_FORMAT_UYVY:
1619 case GST_VIDEO_FORMAT_YUY2:
1620 case GST_VIDEO_FORMAT_v308:
1621 gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
1623 case GST_VIDEO_FORMAT_xRGB:
1624 gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
1626 case GST_VIDEO_FORMAT_xBGR:
1627 gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
1629 case GST_VIDEO_FORMAT_BGRx:
1630 gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
1632 case GST_VIDEO_FORMAT_RGBx:
1633 gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
1635 case GST_VIDEO_FORMAT_ARGB:
1636 gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
1638 case GST_VIDEO_FORMAT_ABGR:
1639 gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
1641 case GST_VIDEO_FORMAT_RGBA:
1642 gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
1644 case GST_VIDEO_FORMAT_BGRA:
1645 gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
1647 case GST_VIDEO_FORMAT_BGR:
1648 case GST_VIDEO_FORMAT_RGB:
1649 gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
1651 case GST_VIDEO_FORMAT_IYU1:
1652 gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
1655 GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
1656 gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
1661 static GstFlowReturn
1662 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1663 GstBuffer * video_frame)
1665 GstVideoFrame frame;
1667 if (overlay->composition == NULL)
1670 if (gst_pad_check_reconfigure (overlay->srcpad))
1671 gst_base_text_overlay_negotiate (overlay);
1673 video_frame = gst_buffer_make_writable (video_frame);
1675 if (overlay->attach_compo_to_buffer) {
1676 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1677 gst_buffer_add_video_overlay_composition_meta (video_frame,
1678 overlay->composition);
1679 /* FIXME: emulate shaded background box if want_shading=true */
1683 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1687 /* shaded background box */
1688 if (overlay->want_shading) {
1691 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1693 gst_base_text_overlay_shade_background (overlay, &frame,
1694 xpos, xpos + overlay->image_width, ypos, ypos + overlay->image_height);
1697 gst_video_overlay_composition_blend (overlay->composition, &frame);
1699 gst_video_frame_unmap (&frame);
1703 return gst_pad_push (overlay->srcpad, video_frame);
1708 gst_buffer_unref (video_frame);
1709 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1714 static GstPadLinkReturn
1715 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
1718 GstBaseTextOverlay *overlay;
1720 overlay = GST_BASE_TEXT_OVERLAY (parent);
1721 if (G_UNLIKELY (!overlay))
1722 return GST_PAD_LINK_REFUSED;
1724 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1726 overlay->text_linked = TRUE;
1728 return GST_PAD_LINK_OK;
1732 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
1734 GstBaseTextOverlay *overlay;
1736 /* don't use gst_pad_get_parent() here, will deadlock */
1737 overlay = GST_BASE_TEXT_OVERLAY (parent);
1739 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1741 overlay->text_linked = FALSE;
1743 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1747 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1750 gboolean ret = FALSE;
1751 GstBaseTextOverlay *overlay = NULL;
1753 overlay = GST_BASE_TEXT_OVERLAY (parent);
1755 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1757 switch (GST_EVENT_TYPE (event)) {
1758 case GST_EVENT_CAPS:
1762 gst_event_parse_caps (event, &caps);
1763 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1764 gst_event_unref (event);
1767 case GST_EVENT_SEGMENT:
1769 const GstSegment *segment;
1771 overlay->text_eos = FALSE;
1773 gst_event_parse_segment (event, &segment);
1775 if (segment->format == GST_FORMAT_TIME) {
1776 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1777 gst_segment_copy_into (segment, &overlay->text_segment);
1778 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1779 &overlay->text_segment);
1780 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1782 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1783 ("received non-TIME newsegment event on text input"));
1786 gst_event_unref (event);
1789 /* wake up the video chain, it might be waiting for a text buffer or
1790 * a text segment update */
1791 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1792 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1793 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1798 GstClockTime start, duration;
1800 gst_event_parse_gap (event, &start, &duration);
1801 if (GST_CLOCK_TIME_IS_VALID (duration))
1803 /* we do not expect another buffer until after gap,
1804 * so that is our position now */
1805 overlay->text_segment.position = start;
1807 /* wake up the video chain, it might be waiting for a text buffer or
1808 * a text segment update */
1809 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1810 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1811 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1814 case GST_EVENT_FLUSH_STOP:
1815 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1816 GST_INFO_OBJECT (overlay, "text flush stop");
1817 overlay->text_flushing = FALSE;
1818 overlay->text_eos = FALSE;
1819 gst_base_text_overlay_pop_text (overlay);
1820 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1821 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1822 gst_event_unref (event);
1825 case GST_EVENT_FLUSH_START:
1826 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1827 GST_INFO_OBJECT (overlay, "text flush start");
1828 overlay->text_flushing = TRUE;
1829 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1830 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1831 gst_event_unref (event);
1835 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1836 overlay->text_eos = TRUE;
1837 GST_INFO_OBJECT (overlay, "text EOS");
1838 /* wake up the video chain, it might be waiting for a text buffer or
1839 * a text segment update */
1840 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1841 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1842 gst_event_unref (event);
1846 ret = gst_pad_event_default (pad, parent, event);
1854 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1857 gboolean ret = FALSE;
1858 GstBaseTextOverlay *overlay = NULL;
1860 overlay = GST_BASE_TEXT_OVERLAY (parent);
1862 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1864 switch (GST_EVENT_TYPE (event)) {
1865 case GST_EVENT_CAPS:
1869 gst_event_parse_caps (event, &caps);
1870 ret = gst_base_text_overlay_setcaps (overlay, caps);
1871 gst_event_unref (event);
1874 case GST_EVENT_SEGMENT:
1876 const GstSegment *segment;
1878 GST_DEBUG_OBJECT (overlay, "received new segment");
1880 gst_event_parse_segment (event, &segment);
1882 if (segment->format == GST_FORMAT_TIME) {
1883 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1886 gst_segment_copy_into (segment, &overlay->segment);
1888 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1889 ("received non-TIME newsegment event on video input"));
1892 ret = gst_pad_event_default (pad, parent, event);
1896 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1897 GST_INFO_OBJECT (overlay, "video EOS");
1898 overlay->video_eos = TRUE;
1899 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1900 ret = gst_pad_event_default (pad, parent, event);
1902 case GST_EVENT_FLUSH_START:
1903 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1904 GST_INFO_OBJECT (overlay, "video flush start");
1905 overlay->video_flushing = TRUE;
1906 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1907 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1908 ret = gst_pad_event_default (pad, parent, event);
1910 case GST_EVENT_FLUSH_STOP:
1911 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1912 GST_INFO_OBJECT (overlay, "video flush stop");
1913 overlay->video_flushing = FALSE;
1914 overlay->video_eos = FALSE;
1915 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1916 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1917 ret = gst_pad_event_default (pad, parent, event);
1920 ret = gst_pad_event_default (pad, parent, event);
1928 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1931 gboolean ret = FALSE;
1932 GstBaseTextOverlay *overlay;
1934 overlay = GST_BASE_TEXT_OVERLAY (parent);
1936 switch (GST_QUERY_TYPE (query)) {
1937 case GST_QUERY_CAPS:
1939 GstCaps *filter, *caps;
1941 gst_query_parse_caps (query, &filter);
1942 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1943 gst_query_set_caps_result (query, caps);
1944 gst_caps_unref (caps);
1949 ret = gst_pad_query_default (pad, parent, query);
1956 /* Called with lock held */
1958 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1960 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1962 if (overlay->text_buffer) {
1963 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1964 overlay->text_buffer);
1965 gst_buffer_unref (overlay->text_buffer);
1966 overlay->text_buffer = NULL;
1969 /* Let the text task know we used that buffer */
1970 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1973 /* We receive text buffers here. If they are out of segment we just ignore them.
1974 If the buffer is in our segment we keep it internally except if another one
1975 is already waiting here, in that case we wait that it gets kicked out */
1976 static GstFlowReturn
1977 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1980 GstFlowReturn ret = GST_FLOW_OK;
1981 GstBaseTextOverlay *overlay = NULL;
1982 gboolean in_seg = FALSE;
1983 guint64 clip_start = 0, clip_stop = 0;
1985 overlay = GST_BASE_TEXT_OVERLAY (parent);
1987 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1989 if (overlay->text_flushing) {
1990 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1991 ret = GST_FLOW_FLUSHING;
1992 GST_LOG_OBJECT (overlay, "text flushing");
1996 if (overlay->text_eos) {
1997 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1999 GST_LOG_OBJECT (overlay, "text EOS");
2003 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2004 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2005 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2006 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2007 GST_BUFFER_DURATION (buffer)));
2009 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2012 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2013 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2015 stop = GST_CLOCK_TIME_NONE;
2017 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2018 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2024 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2025 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2026 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2027 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2029 /* Wait for the previous buffer to go away */
2030 while (overlay->text_buffer != NULL) {
2031 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2032 GST_DEBUG_PAD_NAME (pad));
2033 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2034 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2035 if (overlay->text_flushing) {
2036 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2037 ret = GST_FLOW_FLUSHING;
2042 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2043 overlay->text_segment.position = clip_start;
2045 overlay->text_buffer = buffer;
2046 /* That's a new text buffer we need to render */
2047 overlay->need_render = TRUE;
2049 /* in case the video chain is waiting for a text buffer, wake it up */
2050 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2053 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2060 static GstFlowReturn
2061 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2064 GstBaseTextOverlayClass *klass;
2065 GstBaseTextOverlay *overlay;
2066 GstFlowReturn ret = GST_FLOW_OK;
2067 gboolean in_seg = FALSE;
2068 guint64 start, stop, clip_start = 0, clip_stop = 0;
2071 overlay = GST_BASE_TEXT_OVERLAY (parent);
2072 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2074 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2075 goto missing_timestamp;
2077 /* ignore buffers that are outside of the current segment */
2078 start = GST_BUFFER_TIMESTAMP (buffer);
2080 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2081 stop = GST_CLOCK_TIME_NONE;
2083 stop = start + GST_BUFFER_DURATION (buffer);
2086 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2087 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2088 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2090 /* segment_clip() will adjust start unconditionally to segment_start if
2091 * no stop time is provided, so handle this ourselves */
2092 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2093 goto out_of_segment;
2095 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2096 &clip_start, &clip_stop);
2099 goto out_of_segment;
2101 /* if the buffer is only partially in the segment, fix up stamps */
2102 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2103 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2104 buffer = gst_buffer_make_writable (buffer);
2105 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2107 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2110 /* now, after we've done the clipping, fix up end time if there's no
2111 * duration (we only use those estimated values internally though, we
2112 * don't want to set bogus values on the buffer itself) */
2116 gint fps_num, fps_denom;
2118 /* FIXME, store this in setcaps */
2119 caps = gst_pad_get_current_caps (pad);
2120 s = gst_caps_get_structure (caps, 0);
2121 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2122 fps_num && fps_denom) {
2123 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2124 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2126 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2127 stop = start + 1; /* we need to assume some interval */
2129 gst_caps_unref (caps);
2132 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2136 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2138 if (overlay->video_flushing)
2141 if (overlay->video_eos)
2144 if (overlay->silent) {
2145 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2146 ret = gst_pad_push (overlay->srcpad, buffer);
2148 /* Update position */
2149 overlay->segment.position = clip_start;
2154 /* Text pad not linked, rendering internal text */
2155 if (!overlay->text_linked) {
2156 if (klass->get_text) {
2157 text = klass->get_text (overlay, buffer);
2159 text = g_strdup (overlay->default_text);
2162 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2163 "text: '%s'", GST_STR_NULL (text));
2165 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2167 if (text != NULL && *text != '\0') {
2168 /* Render and push */
2169 gst_base_text_overlay_render_text (overlay, text, -1);
2170 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2172 /* Invalid or empty string */
2173 ret = gst_pad_push (overlay->srcpad, buffer);
2176 /* Text pad linked, check if we have a text buffer queued */
2177 if (overlay->text_buffer) {
2178 gboolean pop_text = FALSE, valid_text_time = TRUE;
2179 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2180 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2181 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2182 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2183 GstClockTime vid_running_time, vid_running_time_end;
2185 /* if the text buffer isn't stamped right, pop it off the
2186 * queue and display it for the current video frame only */
2187 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2188 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2189 GST_WARNING_OBJECT (overlay,
2190 "Got text buffer with invalid timestamp or duration");
2192 valid_text_time = FALSE;
2194 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2195 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2199 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2201 vid_running_time_end =
2202 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2205 /* If timestamp and duration are valid */
2206 if (valid_text_time) {
2208 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2210 text_running_time_end =
2211 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2215 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2216 GST_TIME_ARGS (text_running_time),
2217 GST_TIME_ARGS (text_running_time_end));
2218 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2219 GST_TIME_ARGS (vid_running_time),
2220 GST_TIME_ARGS (vid_running_time_end));
2222 /* Text too old or in the future */
2223 if (valid_text_time && text_running_time_end <= vid_running_time) {
2224 /* text buffer too old, get rid of it and do nothing */
2225 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2227 gst_base_text_overlay_pop_text (overlay);
2228 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2229 goto wait_for_text_buf;
2230 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2231 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2232 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2233 /* Push the video frame */
2234 ret = gst_pad_push (overlay->srcpad, buffer);
2240 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2241 in_text = (gchar *) map.data;
2245 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2246 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2247 * here on purpose, this is something that needs fixing upstream */
2248 if (!g_utf8_validate (in_text, in_size, NULL)) {
2249 const gchar *end = NULL;
2251 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2252 in_text = g_strndup (in_text, in_size);
2253 while (!g_utf8_validate (in_text, in_size, &end) && end)
2254 *((gchar *) end) = '*';
2257 /* Get the string */
2258 if (overlay->have_pango_markup) {
2259 text = g_strndup (in_text, in_size);
2261 text = g_markup_escape_text (in_text, in_size);
2264 if (text != NULL && *text != '\0') {
2265 gint text_len = strlen (text);
2267 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2268 text[text_len - 1] == '\r')) {
2271 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2272 gst_base_text_overlay_render_text (overlay, text, text_len);
2274 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2275 gst_base_text_overlay_render_text (overlay, " ", 1);
2277 if (in_text != (gchar *) map.data)
2280 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2281 gst_base_text_overlay_render_text (overlay, " ", 1);
2284 gst_buffer_unmap (overlay->text_buffer, &map);
2286 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2287 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2289 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2290 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2295 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2296 gst_base_text_overlay_pop_text (overlay);
2297 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2300 gboolean wait_for_text_buf = TRUE;
2302 if (overlay->text_eos)
2303 wait_for_text_buf = FALSE;
2305 if (!overlay->wait_text)
2306 wait_for_text_buf = FALSE;
2308 /* Text pad linked, but no text buffer available - what now? */
2309 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2310 GstClockTime text_start_running_time, text_position_running_time;
2311 GstClockTime vid_running_time;
2314 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2315 GST_BUFFER_TIMESTAMP (buffer));
2316 text_start_running_time =
2317 gst_segment_to_running_time (&overlay->text_segment,
2318 GST_FORMAT_TIME, overlay->text_segment.start);
2319 text_position_running_time =
2320 gst_segment_to_running_time (&overlay->text_segment,
2321 GST_FORMAT_TIME, overlay->text_segment.position);
2323 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2324 vid_running_time < text_start_running_time) ||
2325 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2326 vid_running_time < text_position_running_time)) {
2327 wait_for_text_buf = FALSE;
2331 if (wait_for_text_buf) {
2332 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2333 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2334 GST_DEBUG_OBJECT (overlay, "resuming");
2335 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2336 goto wait_for_text_buf;
2338 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2339 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2340 ret = gst_pad_push (overlay->srcpad, buffer);
2347 /* Update position */
2348 overlay->segment.position = clip_start;
2354 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2355 gst_buffer_unref (buffer);
2361 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2362 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2363 gst_buffer_unref (buffer);
2364 return GST_FLOW_FLUSHING;
2368 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2369 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2370 gst_buffer_unref (buffer);
2371 return GST_FLOW_EOS;
2375 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2376 gst_buffer_unref (buffer);
2381 static GstStateChangeReturn
2382 gst_base_text_overlay_change_state (GstElement * element,
2383 GstStateChange transition)
2385 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2386 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2388 switch (transition) {
2389 case GST_STATE_CHANGE_PAUSED_TO_READY:
2390 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2391 overlay->text_flushing = TRUE;
2392 overlay->video_flushing = TRUE;
2393 /* pop_text will broadcast on the GCond and thus also make the video
2394 * chain exit if it's waiting for a text buffer */
2395 gst_base_text_overlay_pop_text (overlay);
2396 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2402 ret = parent_class->change_state (element, transition);
2403 if (ret == GST_STATE_CHANGE_FAILURE)
2406 switch (transition) {
2407 case GST_STATE_CHANGE_READY_TO_PAUSED:
2408 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2409 overlay->text_flushing = FALSE;
2410 overlay->video_flushing = FALSE;
2411 overlay->video_eos = FALSE;
2412 overlay->text_eos = FALSE;
2413 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2414 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2415 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2425 plugin_init (GstPlugin * plugin)
2427 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2428 GST_TYPE_TEXT_OVERLAY) ||
2429 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2430 GST_TYPE_TIME_OVERLAY) ||
2431 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2432 GST_TYPE_CLOCK_OVERLAY) ||
2433 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2434 GST_TYPE_TEXT_RENDER)) {
2438 /*texttestsrc_plugin_init(module, plugin); */
2440 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2445 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2446 pango, "Pango-based text rendering and overlay", plugin_init,
2447 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)