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.
424 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
425 g_param_spec_double ("xpos", "horizontal position",
426 "Horizontal position when using position alignment", 0, 1.0,
428 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
430 * GstBaseTextOverlay:ypos:
432 * Vertical position of the rendered text when using positioned alignment.
434 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
435 g_param_spec_double ("ypos", "vertical position",
436 "Vertical position when using position alignment", 0, 1.0,
438 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
439 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
440 g_param_spec_enum ("wrap-mode", "wrap mode",
441 "Whether to wrap the text and if so how.",
442 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
443 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
444 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
445 g_param_spec_string ("font-desc", "font description",
446 "Pango font description of font to be used for rendering. "
447 "See documentation of pango_font_description_from_string "
448 "for syntax.", DEFAULT_PROP_FONT_DESC,
449 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
451 * GstBaseTextOverlay:color:
453 * Color of the rendered text.
455 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
456 g_param_spec_uint ("color", "Color",
457 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
459 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
461 * GstTextOverlay:outline-color:
463 * Color of the outline of the rendered text.
465 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
466 g_param_spec_uint ("outline-color", "Text Outline Color",
467 "Color to use for outline the text (big-endian ARGB).", 0,
468 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
469 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
472 * GstBaseTextOverlay:line-alignment:
474 * Alignment of text lines relative to each other (for multi-line text)
476 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
477 g_param_spec_enum ("line-alignment", "line alignment",
478 "Alignment of text lines relative to each other.",
479 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
480 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
482 * GstBaseTextOverlay:silent:
484 * If set, no text is rendered. Useful to switch off text rendering
485 * temporarily without removing the textoverlay element from the pipeline.
487 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
488 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
489 g_param_spec_boolean ("silent", "silent",
490 "Whether to render the text string",
492 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
494 * GstBaseTextOverlay:wait-text:
496 * If set, the video will block until a subtitle is received on the text pad.
497 * If video and subtitles are sent in sync, like from the same demuxer, this
498 * property should be set.
500 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
501 g_param_spec_boolean ("wait-text", "Wait Text",
502 "Whether to wait for subtitles",
503 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
505 g_object_class_install_property (G_OBJECT_CLASS (klass),
506 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
507 "Automatically adjust font size to screen-size.",
508 DEFAULT_PROP_AUTO_ADJUST_SIZE,
509 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
511 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
512 g_param_spec_boolean ("vertical-render", "vertical render",
513 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
514 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
518 gst_base_text_overlay_finalize (GObject * object)
520 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
522 g_free (overlay->default_text);
524 if (overlay->composition) {
525 gst_video_overlay_composition_unref (overlay->composition);
526 overlay->composition = NULL;
529 if (overlay->text_image) {
530 gst_buffer_unref (overlay->text_image);
531 overlay->text_image = NULL;
534 if (overlay->layout) {
535 g_object_unref (overlay->layout);
536 overlay->layout = NULL;
539 if (overlay->text_buffer) {
540 gst_buffer_unref (overlay->text_buffer);
541 overlay->text_buffer = NULL;
544 g_mutex_clear (&overlay->lock);
545 g_cond_clear (&overlay->cond);
547 G_OBJECT_CLASS (parent_class)->finalize (object);
551 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
552 GstBaseTextOverlayClass * klass)
554 GstPadTemplate *template;
555 PangoFontDescription *desc;
558 template = gst_static_pad_template_get (&video_sink_template_factory);
559 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
560 gst_object_unref (template);
561 gst_pad_set_event_function (overlay->video_sinkpad,
562 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
563 gst_pad_set_chain_function (overlay->video_sinkpad,
564 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
565 gst_pad_set_query_function (overlay->video_sinkpad,
566 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
567 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
568 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
571 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
575 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
577 gst_pad_set_event_function (overlay->text_sinkpad,
578 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
579 gst_pad_set_chain_function (overlay->text_sinkpad,
580 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
581 gst_pad_set_link_function (overlay->text_sinkpad,
582 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
583 gst_pad_set_unlink_function (overlay->text_sinkpad,
584 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
585 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
589 template = gst_static_pad_template_get (&src_template_factory);
590 overlay->srcpad = gst_pad_new_from_template (template, "src");
591 gst_object_unref (template);
592 gst_pad_set_event_function (overlay->srcpad,
593 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
594 gst_pad_set_query_function (overlay->srcpad,
595 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
596 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
598 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
599 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
601 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
602 (overlay)->pango_context);
604 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
605 (overlay)->pango_context);
606 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
608 overlay->color = DEFAULT_PROP_COLOR;
609 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
610 overlay->halign = DEFAULT_PROP_HALIGNMENT;
611 overlay->valign = DEFAULT_PROP_VALIGNMENT;
612 overlay->xpad = DEFAULT_PROP_XPAD;
613 overlay->ypad = DEFAULT_PROP_YPAD;
614 overlay->deltax = DEFAULT_PROP_DELTAX;
615 overlay->deltay = DEFAULT_PROP_DELTAY;
616 overlay->xpos = DEFAULT_PROP_XPOS;
617 overlay->ypos = DEFAULT_PROP_YPOS;
619 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
621 overlay->want_shading = DEFAULT_PROP_SHADING;
622 overlay->shading_value = DEFAULT_SHADING_VALUE;
623 overlay->silent = DEFAULT_PROP_SILENT;
624 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
625 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
627 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
628 overlay->need_render = TRUE;
629 overlay->text_image = NULL;
630 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
631 gst_base_text_overlay_update_render_mode (overlay);
633 overlay->text_buffer = NULL;
634 overlay->text_linked = FALSE;
635 g_mutex_init (&overlay->lock);
636 g_cond_init (&overlay->cond);
637 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
638 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
642 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
644 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
645 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
646 pango_layout_set_width (overlay->layout, -1);
650 if (overlay->auto_adjust_size) {
651 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
652 if (overlay->use_vertical_render) {
653 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
657 (overlay->use_vertical_render ? overlay->height : overlay->width) *
661 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
662 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
663 pango_layout_set_width (overlay->layout, width);
664 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
669 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
671 PangoMatrix matrix = PANGO_MATRIX_INIT;
672 PangoContext *context = pango_layout_get_context (overlay->layout);
674 if (overlay->use_vertical_render) {
675 pango_matrix_rotate (&matrix, -90);
676 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
677 pango_context_set_matrix (context, &matrix);
678 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
680 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
681 pango_context_set_matrix (context, &matrix);
682 pango_layout_set_alignment (overlay->layout,
683 (PangoAlignment) overlay->line_align);
688 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
690 GstStructure *structure;
693 structure = gst_caps_get_structure (caps, 0);
694 format = gst_structure_get_string (structure, "format");
695 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
700 /* FIXME: upstream nego (e.g. when the video window is resized) */
702 /* only negotiate/query video overlay composition support for now */
704 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
708 gboolean attach = FALSE;
710 GST_DEBUG_OBJECT (overlay, "performing negotiation");
712 target = gst_pad_get_current_caps (overlay->srcpad);
714 if (!target || gst_caps_is_empty (target))
717 /* find supported meta */
718 query = gst_query_new_allocation (target, TRUE);
720 if (!gst_pad_peer_query (overlay->srcpad, query)) {
721 /* no problem, we use the query defaults */
722 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
725 if (gst_query_find_allocation_meta (query,
726 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
729 overlay->attach_compo_to_buffer = attach;
731 gst_query_unref (query);
732 gst_caps_unref (target);
739 gst_caps_unref (target);
745 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
748 gboolean ret = FALSE;
750 if (!gst_video_info_from_caps (&info, caps))
753 overlay->info = info;
754 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
755 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
756 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
758 ret = gst_pad_set_caps (overlay->srcpad, caps);
761 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
762 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
763 gst_base_text_overlay_negotiate (overlay);
764 gst_base_text_overlay_update_wrap_mode (overlay);
765 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
766 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
774 GST_DEBUG_OBJECT (overlay, "could not parse caps");
780 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
781 const GValue * value, GParamSpec * pspec)
783 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
785 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
788 g_free (overlay->default_text);
789 overlay->default_text = g_value_dup_string (value);
790 overlay->need_render = TRUE;
793 overlay->want_shading = g_value_get_boolean (value);
796 overlay->xpad = g_value_get_int (value);
799 overlay->ypad = g_value_get_int (value);
802 overlay->deltax = g_value_get_int (value);
805 overlay->deltay = g_value_get_int (value);
808 overlay->xpos = g_value_get_double (value);
811 overlay->ypos = g_value_get_double (value);
813 case PROP_VALIGNMENT:
814 overlay->valign = g_value_get_enum (value);
816 case PROP_HALIGNMENT:
817 overlay->halign = g_value_get_enum (value);
820 overlay->wrap_mode = g_value_get_enum (value);
821 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
822 gst_base_text_overlay_update_wrap_mode (overlay);
823 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
827 PangoFontDescription *desc;
828 const gchar *fontdesc_str;
830 fontdesc_str = g_value_get_string (value);
831 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
832 desc = pango_font_description_from_string (fontdesc_str);
834 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
835 pango_layout_set_font_description (overlay->layout, desc);
836 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
837 pango_font_description_free (desc);
839 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
842 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
846 overlay->color = g_value_get_uint (value);
848 case PROP_OUTLINE_COLOR:
849 overlay->outline_color = g_value_get_uint (value);
852 overlay->silent = g_value_get_boolean (value);
854 case PROP_LINE_ALIGNMENT:
855 overlay->line_align = g_value_get_enum (value);
856 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
857 pango_layout_set_alignment (overlay->layout,
858 (PangoAlignment) overlay->line_align);
859 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
862 overlay->wait_text = g_value_get_boolean (value);
864 case PROP_AUTO_ADJUST_SIZE:
865 overlay->auto_adjust_size = g_value_get_boolean (value);
866 overlay->need_render = TRUE;
868 case PROP_VERTICAL_RENDER:
869 overlay->use_vertical_render = g_value_get_boolean (value);
870 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
871 gst_base_text_overlay_update_render_mode (overlay);
872 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
873 overlay->need_render = TRUE;
876 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
880 overlay->need_render = TRUE;
881 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
885 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
886 GValue * value, GParamSpec * pspec)
888 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
890 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
893 g_value_set_string (value, overlay->default_text);
896 g_value_set_boolean (value, overlay->want_shading);
899 g_value_set_int (value, overlay->xpad);
902 g_value_set_int (value, overlay->ypad);
905 g_value_set_int (value, overlay->deltax);
908 g_value_set_int (value, overlay->deltay);
911 g_value_set_double (value, overlay->xpos);
914 g_value_set_double (value, overlay->ypos);
916 case PROP_VALIGNMENT:
917 g_value_set_enum (value, overlay->valign);
919 case PROP_HALIGNMENT:
920 g_value_set_enum (value, overlay->halign);
923 g_value_set_enum (value, overlay->wrap_mode);
926 g_value_set_boolean (value, overlay->silent);
928 case PROP_LINE_ALIGNMENT:
929 g_value_set_enum (value, overlay->line_align);
932 g_value_set_boolean (value, overlay->wait_text);
934 case PROP_AUTO_ADJUST_SIZE:
935 g_value_set_boolean (value, overlay->auto_adjust_size);
937 case PROP_VERTICAL_RENDER:
938 g_value_set_boolean (value, overlay->use_vertical_render);
941 g_value_set_uint (value, overlay->color);
943 case PROP_OUTLINE_COLOR:
944 g_value_set_uint (value, overlay->outline_color);
947 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
951 overlay->need_render = TRUE;
952 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
956 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
959 gboolean ret = FALSE;
960 GstBaseTextOverlay *overlay;
962 overlay = GST_BASE_TEXT_OVERLAY (parent);
964 switch (GST_QUERY_TYPE (query)) {
967 GstCaps *filter, *caps;
969 gst_query_parse_caps (query, &filter);
970 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
971 gst_query_set_caps_result (query, caps);
972 gst_caps_unref (caps);
977 ret = gst_pad_query_default (pad, parent, query);
985 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
988 gboolean ret = FALSE;
989 GstBaseTextOverlay *overlay = NULL;
991 overlay = GST_BASE_TEXT_OVERLAY (parent);
993 switch (GST_EVENT_TYPE (event)) {
994 case GST_EVENT_SEEK:{
997 /* We don't handle seek if we have not text pad */
998 if (!overlay->text_linked) {
999 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1000 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1004 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1006 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1008 /* Flush downstream, only for flushing seek */
1009 if (flags & GST_SEEK_FLAG_FLUSH)
1010 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1012 /* Mark ourself as flushing, unblock chains */
1013 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1014 overlay->video_flushing = TRUE;
1015 overlay->text_flushing = TRUE;
1016 gst_base_text_overlay_pop_text (overlay);
1017 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1019 /* Seek on each sink pad */
1020 gst_event_ref (event);
1021 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1023 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1025 gst_event_unref (event);
1030 if (overlay->text_linked) {
1031 gst_event_ref (event);
1032 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1033 gst_pad_push_event (overlay->text_sinkpad, event);
1035 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1046 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1052 if (G_UNLIKELY (!overlay))
1053 return gst_pad_get_pad_template_caps (pad);
1055 if (pad == overlay->srcpad)
1056 otherpad = overlay->video_sinkpad;
1058 otherpad = overlay->srcpad;
1060 /* we can do what the peer can */
1061 caps = gst_pad_peer_query_caps (otherpad, filter);
1063 GstCaps *temp, *templ;
1065 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1067 /* filtered against our padtemplate */
1068 templ = gst_pad_get_pad_template_caps (otherpad);
1069 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1070 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1071 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1072 gst_caps_unref (caps);
1073 gst_caps_unref (templ);
1074 /* this is what we can do */
1077 /* no peer, our padtemplate is enough then */
1078 caps = gst_pad_get_pad_template_caps (pad);
1080 GstCaps *intersection;
1083 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1084 gst_caps_unref (caps);
1085 caps = intersection;
1089 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1095 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1096 PangoFontDescription * desc)
1098 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1099 overlay->shadow_offset = (double) (font_size) / 13.0;
1100 overlay->outline_offset = (double) (font_size) / 15.0;
1101 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1102 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1106 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1107 gint * xpos, gint * ypos)
1110 GstBaseTextOverlayVAlign valign;
1111 GstBaseTextOverlayHAlign halign;
1113 width = overlay->image_width;
1114 height = overlay->image_height;
1116 if (overlay->use_vertical_render)
1117 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1119 halign = overlay->halign;
1122 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1123 *xpos = overlay->xpad;
1125 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1126 *xpos = (overlay->width - width) / 2;
1128 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1129 *xpos = overlay->width - width - overlay->xpad;
1131 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1132 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1133 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1140 *xpos += overlay->deltax;
1142 if (overlay->use_vertical_render)
1143 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1145 valign = overlay->valign;
1148 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1149 *ypos = overlay->height - height - overlay->ypad;
1151 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1152 *ypos = overlay->height - (height + overlay->ypad);
1154 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1155 *ypos = overlay->ypad;
1157 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1158 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1159 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1161 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1162 *ypos = (overlay->height - height) / 2;
1165 *ypos = overlay->ypad;
1168 *ypos += overlay->deltay;
1172 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1175 GstVideoOverlayRectangle *rectangle;
1177 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1179 if (overlay->text_image) {
1180 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1181 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1182 overlay->image_width, overlay->image_height);
1183 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1184 xpos, ypos, overlay->image_width, overlay->image_height,
1185 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1187 if (overlay->composition)
1188 gst_video_overlay_composition_unref (overlay->composition);
1189 overlay->composition = gst_video_overlay_composition_new (rectangle);
1190 gst_video_overlay_rectangle_unref (rectangle);
1192 } else if (overlay->composition) {
1193 gst_video_overlay_composition_unref (overlay->composition);
1194 overlay->composition = NULL;
1199 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1201 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1209 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1210 const gchar * string, gint textlen)
1213 cairo_surface_t *surface;
1214 PangoRectangle ink_rect, logical_rect;
1215 cairo_matrix_t cairo_matrix;
1217 double scalef = 1.0;
1222 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1224 if (overlay->auto_adjust_size) {
1225 /* 640 pixel is default */
1226 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1228 pango_layout_set_width (overlay->layout, -1);
1229 /* set text on pango layout */
1230 pango_layout_set_markup (overlay->layout, string, textlen);
1232 /* get subtitle image size */
1233 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1235 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1237 if (width + overlay->deltax >
1238 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1240 * subtitle image width is larger then overlay width
1241 * so rearrange overlay wrap mode.
1243 gst_base_text_overlay_update_wrap_mode (overlay);
1244 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1245 width = overlay->width;
1249 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1250 if (height > overlay->height) {
1251 height = overlay->height;
1253 if (overlay->use_vertical_render) {
1254 PangoRectangle rect;
1255 PangoContext *context;
1256 PangoMatrix matrix = PANGO_MATRIX_INIT;
1259 context = pango_layout_get_context (overlay->layout);
1261 pango_matrix_rotate (&matrix, -90);
1263 rect.x = rect.y = 0;
1265 rect.height = height;
1266 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1267 matrix.x0 = -rect.x;
1268 matrix.y0 = -rect.y;
1270 pango_context_set_matrix (context, &matrix);
1272 cairo_matrix.xx = matrix.xx;
1273 cairo_matrix.yx = matrix.yx;
1274 cairo_matrix.xy = matrix.xy;
1275 cairo_matrix.yy = matrix.yy;
1276 cairo_matrix.x0 = matrix.x0;
1277 cairo_matrix.y0 = matrix.y0;
1278 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1284 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1287 /* reallocate overlay buffer */
1288 buffer = gst_buffer_new_and_alloc (4 * width * height);
1289 gst_buffer_replace (&overlay->text_image, buffer);
1290 gst_buffer_unref (buffer);
1292 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1293 surface = cairo_image_surface_create_for_data (map.data,
1294 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1295 cr = cairo_create (surface);
1298 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1301 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1303 if (overlay->want_shading)
1304 cairo_paint_with_alpha (cr, overlay->shading_value);
1306 /* apply transformations */
1307 cairo_set_matrix (cr, &cairo_matrix);
1309 /* FIXME: We use show_layout everywhere except for the surface
1310 * because it's really faster and internally does all kinds of
1311 * caching. Unfortunately we have to paint to a cairo path for
1312 * the outline and this is slow. Once Pango supports user fonts
1313 * we should use them, see
1314 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1316 * Idea would the be, to create a cairo user font that
1317 * does shadow, outline, text painting in the
1318 * render_glyph function.
1321 /* draw shadow text */
1323 PangoAttrList *origin_attr, *filtered_attr, *temp_attr;
1325 /* Store a ref on the original attributes for later restoration */
1327 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1328 /* Take a copy of the original attributes, because pango_attr_list_filter
1329 * modifies the passed list */
1330 temp_attr = pango_attr_list_copy (origin_attr);
1332 pango_attr_list_filter (temp_attr,
1333 gst_text_overlay_filter_foreground_attr, NULL);
1334 pango_attr_list_unref (temp_attr);
1337 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1338 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1339 pango_layout_set_attributes (overlay->layout, filtered_attr);
1340 pango_cairo_show_layout (cr, overlay->layout);
1341 pango_layout_set_attributes (overlay->layout, origin_attr);
1342 pango_attr_list_unref (filtered_attr);
1343 pango_attr_list_unref (origin_attr);
1347 a = (overlay->outline_color >> 24) & 0xff;
1348 r = (overlay->outline_color >> 16) & 0xff;
1349 g = (overlay->outline_color >> 8) & 0xff;
1350 b = (overlay->outline_color >> 0) & 0xff;
1352 /* draw outline text */
1354 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1355 cairo_set_line_width (cr, overlay->outline_offset);
1356 pango_cairo_layout_path (cr, overlay->layout);
1360 a = (overlay->color >> 24) & 0xff;
1361 r = (overlay->color >> 16) & 0xff;
1362 g = (overlay->color >> 8) & 0xff;
1363 b = (overlay->color >> 0) & 0xff;
1367 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1368 pango_cairo_show_layout (cr, overlay->layout);
1372 cairo_surface_destroy (surface);
1373 gst_buffer_unmap (buffer, &map);
1374 overlay->image_width = width;
1375 overlay->image_height = height;
1376 overlay->baseline_y = ink_rect.y;
1377 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1379 gst_base_text_overlay_set_composition (overlay);
1383 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1384 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1386 gint i, j, dest_stride;
1389 dest_stride = dest->info.stride[0];
1390 dest_ptr = dest->data[0];
1392 for (i = y0; i < y1; ++i) {
1393 for (j = x0; j < x1; ++j) {
1394 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1396 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1402 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1403 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1406 guint dest_stride, pixel_stride;
1409 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1410 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1411 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1414 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1416 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1419 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1421 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1423 for (i = y0; i < y1; i++) {
1424 for (j = x0; j < x1; j++) {
1428 y_pos = (i * dest_stride) + j * pixel_stride;
1429 y = dest_ptr[y_pos] + overlay->shading_value;
1431 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1436 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1437 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1438 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1440 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1441 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1446 dest_ptr = dest->data[0];
1448 for (i = y0; i < y1; i++) {
1449 for (j = x0; j < x1; j++) {
1452 y_pos = (i * 4 * overlay->width) + j * 4;
1453 for (k = 0; k < 4; k++) {
1454 y = dest_ptr[y_pos + k] + overlay->shading_value;
1455 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1463 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
1464 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1466 const int pstride = 3;
1467 gint y, x, stride, shading_val, tmp;
1470 shading_val = overlay->shading_value;
1471 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1473 for (y = y0; y < y1; ++y) {
1474 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1475 p += (y * stride) + (x0 * pstride);
1476 for (x = x0; x < x1; ++x) {
1477 tmp = *p + shading_val;
1478 *p++ = CLAMP (tmp, 0, 255);
1479 tmp = *p + shading_val;
1480 *p++ = CLAMP (tmp, 0, 255);
1481 tmp = *p + shading_val;
1482 *p++ = CLAMP (tmp, 0, 255);
1488 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
1489 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1491 gint y, x, stride, shading_val, tmp;
1494 shading_val = overlay->shading_value;
1495 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1497 /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
1498 for (y = y0; y < y1; ++y) {
1499 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1500 /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
1501 /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
1502 * unlikely anyone will notice.. */
1503 p += (y * stride) + ((x0 / 2) * 3) + 1;
1504 for (x = x0; x < x1; x += 2) {
1505 tmp = *p + shading_val;
1506 *p++ = CLAMP (tmp, 0, 255);
1507 tmp = *p + shading_val;
1508 *p++ = CLAMP (tmp, 0, 255);
1515 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1516 static inline void \
1517 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1518 gint x0, gint x1, gint y0, gint y1) \
1523 dest_ptr = dest->data[0];\
1525 for (i = y0; i < y1; i++) {\
1526 for (j = x0; j < x1; j++) {\
1528 y_pos = (i * 4 * overlay->width) + j * 4;\
1529 for (k = OFFSET; k < 3+OFFSET; k++) {\
1530 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1531 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1536 ARGB_SHADE_FUNCTION (ARGB, 1);
1537 ARGB_SHADE_FUNCTION (ABGR, 1);
1538 ARGB_SHADE_FUNCTION (RGBA, 0);
1539 ARGB_SHADE_FUNCTION (BGRA, 0);
1542 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1543 const gchar * text, gint textlen)
1547 if (!overlay->need_render) {
1548 GST_DEBUG ("Using previously rendered text.");
1552 /* -1 is the whole string */
1553 if (text != NULL && textlen < 0) {
1554 textlen = strlen (text);
1558 string = g_strndup (text, textlen);
1559 } else { /* empty string */
1560 string = g_strdup (" ");
1562 g_strdelimit (string, "\r\t", ' ');
1563 textlen = strlen (string);
1565 /* FIXME: should we check for UTF-8 here? */
1567 GST_DEBUG ("Rendering '%s'", string);
1568 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1572 overlay->need_render = FALSE;
1575 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
1580 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
1581 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1583 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1584 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1586 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1587 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1589 switch (overlay->format) {
1590 case GST_VIDEO_FORMAT_I420:
1591 case GST_VIDEO_FORMAT_YV12:
1592 case GST_VIDEO_FORMAT_NV12:
1593 case GST_VIDEO_FORMAT_NV21:
1594 case GST_VIDEO_FORMAT_Y41B:
1595 case GST_VIDEO_FORMAT_Y42B:
1596 case GST_VIDEO_FORMAT_Y444:
1597 case GST_VIDEO_FORMAT_YUV9:
1598 case GST_VIDEO_FORMAT_YVU9:
1599 case GST_VIDEO_FORMAT_GRAY8:
1600 case GST_VIDEO_FORMAT_A420:
1601 gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
1603 case GST_VIDEO_FORMAT_AYUV:
1604 case GST_VIDEO_FORMAT_UYVY:
1605 case GST_VIDEO_FORMAT_YUY2:
1606 case GST_VIDEO_FORMAT_v308:
1607 gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
1609 case GST_VIDEO_FORMAT_xRGB:
1610 gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
1612 case GST_VIDEO_FORMAT_xBGR:
1613 gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
1615 case GST_VIDEO_FORMAT_BGRx:
1616 gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
1618 case GST_VIDEO_FORMAT_RGBx:
1619 gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
1621 case GST_VIDEO_FORMAT_ARGB:
1622 gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
1624 case GST_VIDEO_FORMAT_ABGR:
1625 gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
1627 case GST_VIDEO_FORMAT_RGBA:
1628 gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
1630 case GST_VIDEO_FORMAT_BGRA:
1631 gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
1633 case GST_VIDEO_FORMAT_BGR:
1634 case GST_VIDEO_FORMAT_RGB:
1635 gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
1637 case GST_VIDEO_FORMAT_IYU1:
1638 gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
1641 GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
1642 gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
1647 static GstFlowReturn
1648 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1649 GstBuffer * video_frame)
1651 GstVideoFrame frame;
1653 if (overlay->composition == NULL)
1656 if (gst_pad_check_reconfigure (overlay->srcpad))
1657 gst_base_text_overlay_negotiate (overlay);
1659 video_frame = gst_buffer_make_writable (video_frame);
1661 if (overlay->attach_compo_to_buffer) {
1662 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1663 gst_buffer_add_video_overlay_composition_meta (video_frame,
1664 overlay->composition);
1665 /* FIXME: emulate shaded background box if want_shading=true */
1669 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1673 /* shaded background box */
1674 if (overlay->want_shading) {
1677 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1679 gst_base_text_overlay_shade_background (overlay, &frame,
1680 xpos, xpos + overlay->image_width, ypos, ypos + overlay->image_height);
1683 gst_video_overlay_composition_blend (overlay->composition, &frame);
1685 gst_video_frame_unmap (&frame);
1689 return gst_pad_push (overlay->srcpad, video_frame);
1694 gst_buffer_unref (video_frame);
1695 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1700 static GstPadLinkReturn
1701 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
1704 GstBaseTextOverlay *overlay;
1706 overlay = GST_BASE_TEXT_OVERLAY (parent);
1707 if (G_UNLIKELY (!overlay))
1708 return GST_PAD_LINK_REFUSED;
1710 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1712 overlay->text_linked = TRUE;
1714 return GST_PAD_LINK_OK;
1718 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
1720 GstBaseTextOverlay *overlay;
1722 /* don't use gst_pad_get_parent() here, will deadlock */
1723 overlay = GST_BASE_TEXT_OVERLAY (parent);
1725 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1727 overlay->text_linked = FALSE;
1729 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1733 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1736 gboolean ret = FALSE;
1737 GstBaseTextOverlay *overlay = NULL;
1739 overlay = GST_BASE_TEXT_OVERLAY (parent);
1741 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1743 switch (GST_EVENT_TYPE (event)) {
1744 case GST_EVENT_CAPS:
1748 gst_event_parse_caps (event, &caps);
1749 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1750 gst_event_unref (event);
1753 case GST_EVENT_SEGMENT:
1755 const GstSegment *segment;
1757 overlay->text_eos = FALSE;
1759 gst_event_parse_segment (event, &segment);
1761 if (segment->format == GST_FORMAT_TIME) {
1762 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1763 gst_segment_copy_into (segment, &overlay->text_segment);
1764 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1765 &overlay->text_segment);
1766 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1768 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1769 ("received non-TIME newsegment event on text input"));
1772 gst_event_unref (event);
1775 /* wake up the video chain, it might be waiting for a text buffer or
1776 * a text segment update */
1777 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1778 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1779 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1784 GstClockTime start, duration;
1786 gst_event_parse_gap (event, &start, &duration);
1787 if (GST_CLOCK_TIME_IS_VALID (duration))
1789 /* we do not expect another buffer until after gap,
1790 * so that is our position now */
1791 overlay->text_segment.position = start;
1793 /* wake up the video chain, it might be waiting for a text buffer or
1794 * a text segment update */
1795 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1796 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1797 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1800 case GST_EVENT_FLUSH_STOP:
1801 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1802 GST_INFO_OBJECT (overlay, "text flush stop");
1803 overlay->text_flushing = FALSE;
1804 overlay->text_eos = FALSE;
1805 gst_base_text_overlay_pop_text (overlay);
1806 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1807 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1808 gst_event_unref (event);
1811 case GST_EVENT_FLUSH_START:
1812 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1813 GST_INFO_OBJECT (overlay, "text flush start");
1814 overlay->text_flushing = TRUE;
1815 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1816 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1817 gst_event_unref (event);
1821 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1822 overlay->text_eos = TRUE;
1823 GST_INFO_OBJECT (overlay, "text EOS");
1824 /* wake up the video chain, it might be waiting for a text buffer or
1825 * a text segment update */
1826 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1827 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1828 gst_event_unref (event);
1832 ret = gst_pad_event_default (pad, parent, event);
1840 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1843 gboolean ret = FALSE;
1844 GstBaseTextOverlay *overlay = NULL;
1846 overlay = GST_BASE_TEXT_OVERLAY (parent);
1848 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1850 switch (GST_EVENT_TYPE (event)) {
1851 case GST_EVENT_CAPS:
1855 gst_event_parse_caps (event, &caps);
1856 ret = gst_base_text_overlay_setcaps (overlay, caps);
1857 gst_event_unref (event);
1860 case GST_EVENT_SEGMENT:
1862 const GstSegment *segment;
1864 GST_DEBUG_OBJECT (overlay, "received new segment");
1866 gst_event_parse_segment (event, &segment);
1868 if (segment->format == GST_FORMAT_TIME) {
1869 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1872 gst_segment_copy_into (segment, &overlay->segment);
1874 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1875 ("received non-TIME newsegment event on video input"));
1878 ret = gst_pad_event_default (pad, parent, event);
1882 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1883 GST_INFO_OBJECT (overlay, "video EOS");
1884 overlay->video_eos = TRUE;
1885 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1886 ret = gst_pad_event_default (pad, parent, event);
1888 case GST_EVENT_FLUSH_START:
1889 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1890 GST_INFO_OBJECT (overlay, "video flush start");
1891 overlay->video_flushing = TRUE;
1892 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1893 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1894 ret = gst_pad_event_default (pad, parent, event);
1896 case GST_EVENT_FLUSH_STOP:
1897 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1898 GST_INFO_OBJECT (overlay, "video flush stop");
1899 overlay->video_flushing = FALSE;
1900 overlay->video_eos = FALSE;
1901 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1902 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1903 ret = gst_pad_event_default (pad, parent, event);
1906 ret = gst_pad_event_default (pad, parent, event);
1914 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1917 gboolean ret = FALSE;
1918 GstBaseTextOverlay *overlay;
1920 overlay = GST_BASE_TEXT_OVERLAY (parent);
1922 switch (GST_QUERY_TYPE (query)) {
1923 case GST_QUERY_CAPS:
1925 GstCaps *filter, *caps;
1927 gst_query_parse_caps (query, &filter);
1928 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1929 gst_query_set_caps_result (query, caps);
1930 gst_caps_unref (caps);
1935 ret = gst_pad_query_default (pad, parent, query);
1942 /* Called with lock held */
1944 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1946 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1948 if (overlay->text_buffer) {
1949 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1950 overlay->text_buffer);
1951 gst_buffer_unref (overlay->text_buffer);
1952 overlay->text_buffer = NULL;
1955 /* Let the text task know we used that buffer */
1956 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1959 /* We receive text buffers here. If they are out of segment we just ignore them.
1960 If the buffer is in our segment we keep it internally except if another one
1961 is already waiting here, in that case we wait that it gets kicked out */
1962 static GstFlowReturn
1963 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1966 GstFlowReturn ret = GST_FLOW_OK;
1967 GstBaseTextOverlay *overlay = NULL;
1968 gboolean in_seg = FALSE;
1969 guint64 clip_start = 0, clip_stop = 0;
1971 overlay = GST_BASE_TEXT_OVERLAY (parent);
1973 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1975 if (overlay->text_flushing) {
1976 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1977 ret = GST_FLOW_FLUSHING;
1978 GST_LOG_OBJECT (overlay, "text flushing");
1982 if (overlay->text_eos) {
1983 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1985 GST_LOG_OBJECT (overlay, "text EOS");
1989 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1990 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1991 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1992 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1993 GST_BUFFER_DURATION (buffer)));
1995 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1998 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1999 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2001 stop = GST_CLOCK_TIME_NONE;
2003 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2004 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2010 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2011 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2012 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2013 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2015 /* Wait for the previous buffer to go away */
2016 while (overlay->text_buffer != NULL) {
2017 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2018 GST_DEBUG_PAD_NAME (pad));
2019 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2020 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2021 if (overlay->text_flushing) {
2022 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2023 ret = GST_FLOW_FLUSHING;
2028 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2029 overlay->text_segment.position = clip_start;
2031 overlay->text_buffer = buffer;
2032 /* That's a new text buffer we need to render */
2033 overlay->need_render = TRUE;
2035 /* in case the video chain is waiting for a text buffer, wake it up */
2036 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2039 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2046 static GstFlowReturn
2047 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2050 GstBaseTextOverlayClass *klass;
2051 GstBaseTextOverlay *overlay;
2052 GstFlowReturn ret = GST_FLOW_OK;
2053 gboolean in_seg = FALSE;
2054 guint64 start, stop, clip_start = 0, clip_stop = 0;
2057 overlay = GST_BASE_TEXT_OVERLAY (parent);
2058 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2060 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2061 goto missing_timestamp;
2063 /* ignore buffers that are outside of the current segment */
2064 start = GST_BUFFER_TIMESTAMP (buffer);
2066 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2067 stop = GST_CLOCK_TIME_NONE;
2069 stop = start + GST_BUFFER_DURATION (buffer);
2072 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2073 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2074 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2076 /* segment_clip() will adjust start unconditionally to segment_start if
2077 * no stop time is provided, so handle this ourselves */
2078 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2079 goto out_of_segment;
2081 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2082 &clip_start, &clip_stop);
2085 goto out_of_segment;
2087 /* if the buffer is only partially in the segment, fix up stamps */
2088 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2089 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2090 buffer = gst_buffer_make_writable (buffer);
2091 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2093 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2096 /* now, after we've done the clipping, fix up end time if there's no
2097 * duration (we only use those estimated values internally though, we
2098 * don't want to set bogus values on the buffer itself) */
2102 gint fps_num, fps_denom;
2104 /* FIXME, store this in setcaps */
2105 caps = gst_pad_get_current_caps (pad);
2106 s = gst_caps_get_structure (caps, 0);
2107 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2108 fps_num && fps_denom) {
2109 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2110 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2112 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2113 stop = start + 1; /* we need to assume some interval */
2115 gst_caps_unref (caps);
2118 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2122 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2124 if (overlay->video_flushing)
2127 if (overlay->video_eos)
2130 if (overlay->silent) {
2131 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2132 ret = gst_pad_push (overlay->srcpad, buffer);
2134 /* Update position */
2135 overlay->segment.position = clip_start;
2140 /* Text pad not linked, rendering internal text */
2141 if (!overlay->text_linked) {
2142 if (klass->get_text) {
2143 text = klass->get_text (overlay, buffer);
2145 text = g_strdup (overlay->default_text);
2148 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2149 "text: '%s'", GST_STR_NULL (text));
2151 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2153 if (text != NULL && *text != '\0') {
2154 /* Render and push */
2155 gst_base_text_overlay_render_text (overlay, text, -1);
2156 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2158 /* Invalid or empty string */
2159 ret = gst_pad_push (overlay->srcpad, buffer);
2162 /* Text pad linked, check if we have a text buffer queued */
2163 if (overlay->text_buffer) {
2164 gboolean pop_text = FALSE, valid_text_time = TRUE;
2165 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2166 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2167 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2168 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2169 GstClockTime vid_running_time, vid_running_time_end;
2171 /* if the text buffer isn't stamped right, pop it off the
2172 * queue and display it for the current video frame only */
2173 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2174 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2175 GST_WARNING_OBJECT (overlay,
2176 "Got text buffer with invalid timestamp or duration");
2178 valid_text_time = FALSE;
2180 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2181 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2185 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2187 vid_running_time_end =
2188 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2191 /* If timestamp and duration are valid */
2192 if (valid_text_time) {
2194 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2196 text_running_time_end =
2197 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2201 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2202 GST_TIME_ARGS (text_running_time),
2203 GST_TIME_ARGS (text_running_time_end));
2204 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2205 GST_TIME_ARGS (vid_running_time),
2206 GST_TIME_ARGS (vid_running_time_end));
2208 /* Text too old or in the future */
2209 if (valid_text_time && text_running_time_end <= vid_running_time) {
2210 /* text buffer too old, get rid of it and do nothing */
2211 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2213 gst_base_text_overlay_pop_text (overlay);
2214 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2215 goto wait_for_text_buf;
2216 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2217 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2218 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2219 /* Push the video frame */
2220 ret = gst_pad_push (overlay->srcpad, buffer);
2226 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2227 in_text = (gchar *) map.data;
2231 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2232 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2233 * here on purpose, this is something that needs fixing upstream */
2234 if (!g_utf8_validate (in_text, in_size, NULL)) {
2235 const gchar *end = NULL;
2237 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2238 in_text = g_strndup (in_text, in_size);
2239 while (!g_utf8_validate (in_text, in_size, &end) && end)
2240 *((gchar *) end) = '*';
2243 /* Get the string */
2244 if (overlay->have_pango_markup) {
2245 text = g_strndup (in_text, in_size);
2247 text = g_markup_escape_text (in_text, in_size);
2250 if (text != NULL && *text != '\0') {
2251 gint text_len = strlen (text);
2253 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2254 text[text_len - 1] == '\r')) {
2257 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2258 gst_base_text_overlay_render_text (overlay, text, text_len);
2260 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2261 gst_base_text_overlay_render_text (overlay, " ", 1);
2263 if (in_text != (gchar *) map.data)
2266 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2267 gst_base_text_overlay_render_text (overlay, " ", 1);
2270 gst_buffer_unmap (overlay->text_buffer, &map);
2272 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2273 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2275 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2276 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2281 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2282 gst_base_text_overlay_pop_text (overlay);
2283 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2286 gboolean wait_for_text_buf = TRUE;
2288 if (overlay->text_eos)
2289 wait_for_text_buf = FALSE;
2291 if (!overlay->wait_text)
2292 wait_for_text_buf = FALSE;
2294 /* Text pad linked, but no text buffer available - what now? */
2295 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2296 GstClockTime text_start_running_time, text_position_running_time;
2297 GstClockTime vid_running_time;
2300 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2301 GST_BUFFER_TIMESTAMP (buffer));
2302 text_start_running_time =
2303 gst_segment_to_running_time (&overlay->text_segment,
2304 GST_FORMAT_TIME, overlay->text_segment.start);
2305 text_position_running_time =
2306 gst_segment_to_running_time (&overlay->text_segment,
2307 GST_FORMAT_TIME, overlay->text_segment.position);
2309 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2310 vid_running_time < text_start_running_time) ||
2311 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2312 vid_running_time < text_position_running_time)) {
2313 wait_for_text_buf = FALSE;
2317 if (wait_for_text_buf) {
2318 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2319 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2320 GST_DEBUG_OBJECT (overlay, "resuming");
2321 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2322 goto wait_for_text_buf;
2324 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2325 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2326 ret = gst_pad_push (overlay->srcpad, buffer);
2333 /* Update position */
2334 overlay->segment.position = clip_start;
2340 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2341 gst_buffer_unref (buffer);
2347 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2348 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2349 gst_buffer_unref (buffer);
2350 return GST_FLOW_FLUSHING;
2354 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2355 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2356 gst_buffer_unref (buffer);
2357 return GST_FLOW_EOS;
2361 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2362 gst_buffer_unref (buffer);
2367 static GstStateChangeReturn
2368 gst_base_text_overlay_change_state (GstElement * element,
2369 GstStateChange transition)
2371 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2372 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2374 switch (transition) {
2375 case GST_STATE_CHANGE_PAUSED_TO_READY:
2376 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2377 overlay->text_flushing = TRUE;
2378 overlay->video_flushing = TRUE;
2379 /* pop_text will broadcast on the GCond and thus also make the video
2380 * chain exit if it's waiting for a text buffer */
2381 gst_base_text_overlay_pop_text (overlay);
2382 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2388 ret = parent_class->change_state (element, transition);
2389 if (ret == GST_STATE_CHANGE_FAILURE)
2392 switch (transition) {
2393 case GST_STATE_CHANGE_READY_TO_PAUSED:
2394 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2395 overlay->text_flushing = FALSE;
2396 overlay->video_flushing = FALSE;
2397 overlay->video_eos = FALSE;
2398 overlay->text_eos = FALSE;
2399 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2400 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2401 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2411 plugin_init (GstPlugin * plugin)
2413 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2414 GST_TYPE_TEXT_OVERLAY) ||
2415 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2416 GST_TYPE_TIME_OVERLAY) ||
2417 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2418 GST_TYPE_CLOCK_OVERLAY) ||
2419 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2420 GST_TYPE_TEXT_RENDER)) {
2424 /*texttestsrc_plugin_init(module, plugin); */
2426 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2431 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2432 pango, "Pango-based text rendering and overlay", plugin_init,
2433 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)