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 /* FIXME: video-blend.c doesn't support formats with more than 8 bit per
151 * component (which get unpacked into ARGB64 or AYUV64) yet, such as:
152 * v210, v216, UYVP, GRAY16_LE, GRAY16_BE */
153 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
154 I420, YV12, AYUV, YUY2, UYVY, v308, Y41B, Y42B, Y444, \
155 NV12, NV21, A420, YUV9, YVU9, IYU1, GRAY8 }"
157 static GstStaticPadTemplate src_template_factory =
158 GST_STATIC_PAD_TEMPLATE ("src",
161 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
164 static GstStaticPadTemplate video_sink_template_factory =
165 GST_STATIC_PAD_TEMPLATE ("video_sink",
168 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
171 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
173 gst_base_text_overlay_valign_get_type (void)
175 static GType base_text_overlay_valign_type = 0;
176 static const GEnumValue base_text_overlay_valign[] = {
177 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
178 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
179 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
180 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
181 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
185 if (!base_text_overlay_valign_type) {
186 base_text_overlay_valign_type =
187 g_enum_register_static ("GstBaseTextOverlayVAlign",
188 base_text_overlay_valign);
190 return base_text_overlay_valign_type;
193 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
195 gst_base_text_overlay_halign_get_type (void)
197 static GType base_text_overlay_halign_type = 0;
198 static const GEnumValue base_text_overlay_halign[] = {
199 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
200 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
201 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
202 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
206 if (!base_text_overlay_halign_type) {
207 base_text_overlay_halign_type =
208 g_enum_register_static ("GstBaseTextOverlayHAlign",
209 base_text_overlay_halign);
211 return base_text_overlay_halign_type;
215 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
217 gst_base_text_overlay_wrap_mode_get_type (void)
219 static GType base_text_overlay_wrap_mode_type = 0;
220 static const GEnumValue base_text_overlay_wrap_mode[] = {
221 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
222 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
223 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
224 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
228 if (!base_text_overlay_wrap_mode_type) {
229 base_text_overlay_wrap_mode_type =
230 g_enum_register_static ("GstBaseTextOverlayWrapMode",
231 base_text_overlay_wrap_mode);
233 return base_text_overlay_wrap_mode_type;
236 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
238 gst_base_text_overlay_line_align_get_type (void)
240 static GType base_text_overlay_line_align_type = 0;
241 static const GEnumValue base_text_overlay_line_align[] = {
242 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
243 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
244 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
248 if (!base_text_overlay_line_align_type) {
249 base_text_overlay_line_align_type =
250 g_enum_register_static ("GstBaseTextOverlayLineAlign",
251 base_text_overlay_line_align);
253 return base_text_overlay_line_align_type;
256 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
257 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
258 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
259 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
260 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
261 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
262 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
264 static GstElementClass *parent_class = NULL;
265 static void gst_base_text_overlay_base_init (gpointer g_class);
266 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
267 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
268 GstBaseTextOverlayClass * klass);
270 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
271 element, GstStateChange transition);
273 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
274 GstBaseTextOverlay * overlay, GstCaps * filter);
275 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
277 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
279 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
280 GstObject * parent, GstEvent * event);
281 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
282 GstObject * parent, GstQuery * query);
284 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
285 GstObject * parent, GstEvent * event);
286 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
287 GstObject * parent, GstQuery * query);
288 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
289 GstObject * parent, GstBuffer * buffer);
291 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
292 GstObject * parent, GstEvent * event);
293 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
294 GstObject * parent, GstBuffer * buffer);
295 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
296 GstObject * parent, GstPad * peer);
297 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
299 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
300 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
303 static void gst_base_text_overlay_finalize (GObject * object);
304 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
305 const GValue * value, GParamSpec * pspec);
306 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
307 GValue * value, GParamSpec * pspec);
309 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
310 PangoFontDescription * desc);
313 gst_base_text_overlay_get_type (void)
315 static GType type = 0;
317 if (g_once_init_enter ((gsize *) & type)) {
318 static const GTypeInfo info = {
319 sizeof (GstBaseTextOverlayClass),
320 (GBaseInitFunc) gst_base_text_overlay_base_init,
322 (GClassInitFunc) gst_base_text_overlay_class_init,
325 sizeof (GstBaseTextOverlay),
327 (GInstanceInitFunc) gst_base_text_overlay_init,
330 g_once_init_leave ((gsize *) & type,
331 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
339 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
340 GstBuffer * video_frame)
342 return g_strdup (overlay->default_text);
346 gst_base_text_overlay_base_init (gpointer g_class)
348 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
349 PangoFontMap *fontmap;
351 /* Only lock for the subclasses here, the base class
352 * doesn't have this mutex yet and it's not necessary
354 if (klass->pango_lock)
355 g_mutex_lock (klass->pango_lock);
356 fontmap = pango_cairo_font_map_get_default ();
357 klass->pango_context =
358 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
359 if (klass->pango_lock)
360 g_mutex_unlock (klass->pango_lock);
364 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
366 GObjectClass *gobject_class;
367 GstElementClass *gstelement_class;
369 gobject_class = (GObjectClass *) klass;
370 gstelement_class = (GstElementClass *) klass;
372 parent_class = g_type_class_peek_parent (klass);
374 gobject_class->finalize = gst_base_text_overlay_finalize;
375 gobject_class->set_property = gst_base_text_overlay_set_property;
376 gobject_class->get_property = gst_base_text_overlay_get_property;
378 gst_element_class_add_pad_template (gstelement_class,
379 gst_static_pad_template_get (&src_template_factory));
380 gst_element_class_add_pad_template (gstelement_class,
381 gst_static_pad_template_get (&video_sink_template_factory));
383 gstelement_class->change_state =
384 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
386 klass->pango_lock = g_slice_new (GMutex);
387 g_mutex_init (klass->pango_lock);
389 klass->get_text = gst_base_text_overlay_get_text;
391 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
392 g_param_spec_string ("text", "text",
393 "Text to be display.", DEFAULT_PROP_TEXT,
394 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
395 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
396 g_param_spec_boolean ("shaded-background", "shaded background",
397 "Whether to shade the background under the text area",
398 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
399 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
400 g_param_spec_enum ("valignment", "vertical alignment",
401 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
402 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
403 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
404 g_param_spec_enum ("halignment", "horizontal alignment",
405 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
406 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
407 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
408 g_param_spec_int ("xpad", "horizontal paddding",
409 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
410 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
411 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
412 g_param_spec_int ("ypad", "vertical padding",
413 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
414 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
415 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
416 g_param_spec_int ("deltax", "X position modifier",
417 "Shift X position to the left or to the right. Unit is pixels.",
418 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
419 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
420 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
421 g_param_spec_int ("deltay", "Y position modifier",
422 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
423 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
425 * GstBaseTextOverlay:xpos
427 * Horizontal position of the rendered text when using positioned alignment.
431 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
432 g_param_spec_double ("xpos", "horizontal position",
433 "Horizontal position when using position alignment", 0, 1.0,
435 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
437 * GstBaseTextOverlay:ypos
439 * Vertical position of the rendered text when using positioned alignment.
443 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
444 g_param_spec_double ("ypos", "vertical position",
445 "Vertical position when using position alignment", 0, 1.0,
447 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
448 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
449 g_param_spec_enum ("wrap-mode", "wrap mode",
450 "Whether to wrap the text and if so how.",
451 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
452 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
453 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
454 g_param_spec_string ("font-desc", "font description",
455 "Pango font description of font to be used for rendering. "
456 "See documentation of pango_font_description_from_string "
457 "for syntax.", DEFAULT_PROP_FONT_DESC,
458 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
460 * GstBaseTextOverlay:color
462 * Color of the rendered text.
466 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
467 g_param_spec_uint ("color", "Color",
468 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
470 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
472 * GstTextOverlay:outline-color
474 * Color of the outline of the rendered text.
478 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
479 g_param_spec_uint ("outline-color", "Text Outline Color",
480 "Color to use for outline the text (big-endian ARGB).", 0,
481 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
482 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
485 * GstBaseTextOverlay:line-alignment
487 * Alignment of text lines relative to each other (for multi-line text)
491 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
492 g_param_spec_enum ("line-alignment", "line alignment",
493 "Alignment of text lines relative to each other.",
494 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
495 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
497 * GstBaseTextOverlay:silent
499 * If set, no text is rendered. Useful to switch off text rendering
500 * temporarily without removing the textoverlay element from the pipeline.
504 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
505 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
506 g_param_spec_boolean ("silent", "silent",
507 "Whether to render the text string",
509 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
511 * GstBaseTextOverlay:wait-text
513 * If set, the video will block until a subtitle is received on the text pad.
514 * If video and subtitles are sent in sync, like from the same demuxer, this
515 * property should be set.
519 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
520 g_param_spec_boolean ("wait-text", "Wait Text",
521 "Whether to wait for subtitles",
522 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
524 g_object_class_install_property (G_OBJECT_CLASS (klass),
525 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
526 "Automatically adjust font size to screen-size.",
527 DEFAULT_PROP_AUTO_ADJUST_SIZE,
528 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
530 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
531 g_param_spec_boolean ("vertical-render", "vertical render",
532 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
533 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
537 gst_base_text_overlay_finalize (GObject * object)
539 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
541 g_free (overlay->default_text);
543 if (overlay->composition) {
544 gst_video_overlay_composition_unref (overlay->composition);
545 overlay->composition = NULL;
548 if (overlay->text_image) {
549 gst_buffer_unref (overlay->text_image);
550 overlay->text_image = NULL;
553 if (overlay->layout) {
554 g_object_unref (overlay->layout);
555 overlay->layout = NULL;
558 if (overlay->text_buffer) {
559 gst_buffer_unref (overlay->text_buffer);
560 overlay->text_buffer = NULL;
563 g_mutex_clear (&overlay->lock);
564 g_cond_clear (&overlay->cond);
566 G_OBJECT_CLASS (parent_class)->finalize (object);
570 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
571 GstBaseTextOverlayClass * klass)
573 GstPadTemplate *template;
574 PangoFontDescription *desc;
577 template = gst_static_pad_template_get (&video_sink_template_factory);
578 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
579 gst_object_unref (template);
580 gst_pad_set_event_function (overlay->video_sinkpad,
581 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
582 gst_pad_set_chain_function (overlay->video_sinkpad,
583 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
584 gst_pad_set_query_function (overlay->video_sinkpad,
585 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
586 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
587 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
590 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
594 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
596 gst_pad_set_event_function (overlay->text_sinkpad,
597 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
598 gst_pad_set_chain_function (overlay->text_sinkpad,
599 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
600 gst_pad_set_link_function (overlay->text_sinkpad,
601 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
602 gst_pad_set_unlink_function (overlay->text_sinkpad,
603 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
604 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
608 template = gst_static_pad_template_get (&src_template_factory);
609 overlay->srcpad = gst_pad_new_from_template (template, "src");
610 gst_object_unref (template);
611 gst_pad_set_event_function (overlay->srcpad,
612 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
613 gst_pad_set_query_function (overlay->srcpad,
614 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
615 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
617 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
618 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
620 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
621 (overlay)->pango_context);
623 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
624 (overlay)->pango_context);
625 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
627 overlay->color = DEFAULT_PROP_COLOR;
628 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
629 overlay->halign = DEFAULT_PROP_HALIGNMENT;
630 overlay->valign = DEFAULT_PROP_VALIGNMENT;
631 overlay->xpad = DEFAULT_PROP_XPAD;
632 overlay->ypad = DEFAULT_PROP_YPAD;
633 overlay->deltax = DEFAULT_PROP_DELTAX;
634 overlay->deltay = DEFAULT_PROP_DELTAY;
635 overlay->xpos = DEFAULT_PROP_XPOS;
636 overlay->ypos = DEFAULT_PROP_YPOS;
638 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
640 overlay->want_shading = DEFAULT_PROP_SHADING;
641 overlay->shading_value = DEFAULT_SHADING_VALUE;
642 overlay->silent = DEFAULT_PROP_SILENT;
643 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
644 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
646 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
647 overlay->need_render = TRUE;
648 overlay->text_image = NULL;
649 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
650 gst_base_text_overlay_update_render_mode (overlay);
652 overlay->text_buffer = NULL;
653 overlay->text_linked = FALSE;
654 g_mutex_init (&overlay->lock);
655 g_cond_init (&overlay->cond);
656 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
657 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
661 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
663 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
664 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
665 pango_layout_set_width (overlay->layout, -1);
669 if (overlay->auto_adjust_size) {
670 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
671 if (overlay->use_vertical_render) {
672 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
676 (overlay->use_vertical_render ? overlay->height : overlay->width) *
680 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
681 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
682 pango_layout_set_width (overlay->layout, width);
683 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
688 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
690 PangoMatrix matrix = PANGO_MATRIX_INIT;
691 PangoContext *context = pango_layout_get_context (overlay->layout);
693 if (overlay->use_vertical_render) {
694 pango_matrix_rotate (&matrix, -90);
695 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
696 pango_context_set_matrix (context, &matrix);
697 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
699 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
700 pango_context_set_matrix (context, &matrix);
701 pango_layout_set_alignment (overlay->layout,
702 (PangoAlignment) overlay->line_align);
707 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
709 GstStructure *structure;
712 structure = gst_caps_get_structure (caps, 0);
713 format = gst_structure_get_string (structure, "format");
714 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
719 /* FIXME: upstream nego (e.g. when the video window is resized) */
721 /* only negotiate/query video overlay composition support for now */
723 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
727 gboolean attach = FALSE;
729 GST_DEBUG_OBJECT (overlay, "performing negotiation");
731 target = gst_pad_get_current_caps (overlay->srcpad);
733 if (!target || gst_caps_is_empty (target))
736 /* find supported meta */
737 query = gst_query_new_allocation (target, TRUE);
739 if (!gst_pad_peer_query (overlay->srcpad, query)) {
740 /* no problem, we use the query defaults */
741 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
744 if (gst_query_find_allocation_meta (query,
745 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
748 overlay->attach_compo_to_buffer = attach;
750 gst_query_unref (query);
751 gst_caps_unref (target);
758 gst_caps_unref (target);
764 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
767 gboolean ret = FALSE;
769 if (!gst_video_info_from_caps (&info, caps))
772 overlay->info = info;
773 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
774 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
775 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
777 ret = gst_pad_set_caps (overlay->srcpad, caps);
780 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
781 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
782 gst_base_text_overlay_negotiate (overlay);
783 gst_base_text_overlay_update_wrap_mode (overlay);
784 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
785 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
793 GST_DEBUG_OBJECT (overlay, "could not parse caps");
799 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
800 const GValue * value, GParamSpec * pspec)
802 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
804 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
807 g_free (overlay->default_text);
808 overlay->default_text = g_value_dup_string (value);
809 overlay->need_render = TRUE;
812 overlay->want_shading = g_value_get_boolean (value);
815 overlay->xpad = g_value_get_int (value);
818 overlay->ypad = g_value_get_int (value);
821 overlay->deltax = g_value_get_int (value);
824 overlay->deltay = g_value_get_int (value);
827 overlay->xpos = g_value_get_double (value);
830 overlay->ypos = g_value_get_double (value);
832 case PROP_VALIGNMENT:
833 overlay->valign = g_value_get_enum (value);
835 case PROP_HALIGNMENT:
836 overlay->halign = g_value_get_enum (value);
839 overlay->wrap_mode = g_value_get_enum (value);
840 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
841 gst_base_text_overlay_update_wrap_mode (overlay);
842 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
846 PangoFontDescription *desc;
847 const gchar *fontdesc_str;
849 fontdesc_str = g_value_get_string (value);
850 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
851 desc = pango_font_description_from_string (fontdesc_str);
853 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
854 pango_layout_set_font_description (overlay->layout, desc);
855 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
856 pango_font_description_free (desc);
858 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
861 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
865 overlay->color = g_value_get_uint (value);
867 case PROP_OUTLINE_COLOR:
868 overlay->outline_color = g_value_get_uint (value);
871 overlay->silent = g_value_get_boolean (value);
873 case PROP_LINE_ALIGNMENT:
874 overlay->line_align = g_value_get_enum (value);
875 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
876 pango_layout_set_alignment (overlay->layout,
877 (PangoAlignment) overlay->line_align);
878 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
881 overlay->wait_text = g_value_get_boolean (value);
883 case PROP_AUTO_ADJUST_SIZE:
884 overlay->auto_adjust_size = g_value_get_boolean (value);
885 overlay->need_render = TRUE;
887 case PROP_VERTICAL_RENDER:
888 overlay->use_vertical_render = g_value_get_boolean (value);
889 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
890 gst_base_text_overlay_update_render_mode (overlay);
891 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
892 overlay->need_render = TRUE;
895 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
899 overlay->need_render = TRUE;
900 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
904 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
905 GValue * value, GParamSpec * pspec)
907 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
909 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
912 g_value_set_string (value, overlay->default_text);
915 g_value_set_boolean (value, overlay->want_shading);
918 g_value_set_int (value, overlay->xpad);
921 g_value_set_int (value, overlay->ypad);
924 g_value_set_int (value, overlay->deltax);
927 g_value_set_int (value, overlay->deltay);
930 g_value_set_double (value, overlay->xpos);
933 g_value_set_double (value, overlay->ypos);
935 case PROP_VALIGNMENT:
936 g_value_set_enum (value, overlay->valign);
938 case PROP_HALIGNMENT:
939 g_value_set_enum (value, overlay->halign);
942 g_value_set_enum (value, overlay->wrap_mode);
945 g_value_set_boolean (value, overlay->silent);
947 case PROP_LINE_ALIGNMENT:
948 g_value_set_enum (value, overlay->line_align);
951 g_value_set_boolean (value, overlay->wait_text);
953 case PROP_AUTO_ADJUST_SIZE:
954 g_value_set_boolean (value, overlay->auto_adjust_size);
956 case PROP_VERTICAL_RENDER:
957 g_value_set_boolean (value, overlay->use_vertical_render);
960 g_value_set_uint (value, overlay->color);
962 case PROP_OUTLINE_COLOR:
963 g_value_set_uint (value, overlay->outline_color);
966 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
970 overlay->need_render = TRUE;
971 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
975 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
978 gboolean ret = FALSE;
979 GstBaseTextOverlay *overlay;
981 overlay = GST_BASE_TEXT_OVERLAY (parent);
983 switch (GST_QUERY_TYPE (query)) {
986 GstCaps *filter, *caps;
988 gst_query_parse_caps (query, &filter);
989 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
990 gst_query_set_caps_result (query, caps);
991 gst_caps_unref (caps);
996 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1004 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1007 gboolean ret = FALSE;
1008 GstBaseTextOverlay *overlay = NULL;
1010 overlay = GST_BASE_TEXT_OVERLAY (parent);
1012 switch (GST_EVENT_TYPE (event)) {
1013 case GST_EVENT_SEEK:{
1016 /* We don't handle seek if we have not text pad */
1017 if (!overlay->text_linked) {
1018 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1019 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1023 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1025 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1027 /* Flush downstream, only for flushing seek */
1028 if (flags & GST_SEEK_FLAG_FLUSH)
1029 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1031 /* Mark ourself as flushing, unblock chains */
1032 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1033 overlay->video_flushing = TRUE;
1034 overlay->text_flushing = TRUE;
1035 gst_base_text_overlay_pop_text (overlay);
1036 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1038 /* Seek on each sink pad */
1039 gst_event_ref (event);
1040 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1042 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1044 gst_event_unref (event);
1049 if (overlay->text_linked) {
1050 gst_event_ref (event);
1051 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1052 gst_pad_push_event (overlay->text_sinkpad, event);
1054 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1065 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1071 if (G_UNLIKELY (!overlay))
1072 return gst_pad_get_pad_template_caps (pad);
1074 if (pad == overlay->srcpad)
1075 otherpad = overlay->video_sinkpad;
1077 otherpad = overlay->srcpad;
1079 /* we can do what the peer can */
1080 caps = gst_pad_peer_query_caps (otherpad, filter);
1082 GstCaps *temp, *templ;
1084 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1086 /* filtered against our padtemplate */
1087 templ = gst_pad_get_pad_template_caps (otherpad);
1088 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1089 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1090 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1091 gst_caps_unref (caps);
1092 gst_caps_unref (templ);
1093 /* this is what we can do */
1096 /* no peer, our padtemplate is enough then */
1097 caps = gst_pad_get_pad_template_caps (pad);
1099 GstCaps *intersection;
1102 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1103 gst_caps_unref (caps);
1104 caps = intersection;
1108 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1114 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1115 PangoFontDescription * desc)
1117 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1118 overlay->shadow_offset = (double) (font_size) / 13.0;
1119 overlay->outline_offset = (double) (font_size) / 15.0;
1120 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1121 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1125 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1126 gint * xpos, gint * ypos)
1129 GstBaseTextOverlayVAlign valign;
1130 GstBaseTextOverlayHAlign halign;
1132 width = overlay->image_width;
1133 height = overlay->image_height;
1135 if (overlay->use_vertical_render)
1136 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1138 halign = overlay->halign;
1141 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1142 *xpos = overlay->xpad;
1144 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1145 *xpos = (overlay->width - width) / 2;
1147 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1148 *xpos = overlay->width - width - overlay->xpad;
1150 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1151 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1152 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1159 *xpos += overlay->deltax;
1161 if (overlay->use_vertical_render)
1162 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1164 valign = overlay->valign;
1167 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1168 *ypos = overlay->height - height - overlay->ypad;
1170 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1171 *ypos = overlay->height - (height + overlay->ypad);
1173 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1174 *ypos = overlay->ypad;
1176 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1177 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1178 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1180 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1181 *ypos = (overlay->height - height) / 2;
1184 *ypos = overlay->ypad;
1187 *ypos += overlay->deltay;
1191 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1194 GstVideoOverlayRectangle *rectangle;
1196 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1198 if (overlay->text_image) {
1199 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1200 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1201 overlay->image_width, overlay->image_height);
1202 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1203 xpos, ypos, overlay->image_width, overlay->image_height,
1204 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1206 if (overlay->composition)
1207 gst_video_overlay_composition_unref (overlay->composition);
1208 overlay->composition = gst_video_overlay_composition_new (rectangle);
1209 gst_video_overlay_rectangle_unref (rectangle);
1211 } else if (overlay->composition) {
1212 gst_video_overlay_composition_unref (overlay->composition);
1213 overlay->composition = NULL;
1218 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1220 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1228 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1229 const gchar * string, gint textlen)
1232 cairo_surface_t *surface;
1233 PangoRectangle ink_rect, logical_rect;
1234 cairo_matrix_t cairo_matrix;
1236 double scalef = 1.0;
1241 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1243 if (overlay->auto_adjust_size) {
1244 /* 640 pixel is default */
1245 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1247 pango_layout_set_width (overlay->layout, -1);
1248 /* set text on pango layout */
1249 pango_layout_set_markup (overlay->layout, string, textlen);
1251 /* get subtitle image size */
1252 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1254 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1256 if (width + overlay->deltax >
1257 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1259 * subtitle image width is larger then overlay width
1260 * so rearrange overlay wrap mode.
1262 gst_base_text_overlay_update_wrap_mode (overlay);
1263 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1264 width = overlay->width;
1268 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1269 if (height > overlay->height) {
1270 height = overlay->height;
1272 if (overlay->use_vertical_render) {
1273 PangoRectangle rect;
1274 PangoContext *context;
1275 PangoMatrix matrix = PANGO_MATRIX_INIT;
1278 context = pango_layout_get_context (overlay->layout);
1280 pango_matrix_rotate (&matrix, -90);
1282 rect.x = rect.y = 0;
1284 rect.height = height;
1285 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1286 matrix.x0 = -rect.x;
1287 matrix.y0 = -rect.y;
1289 pango_context_set_matrix (context, &matrix);
1291 cairo_matrix.xx = matrix.xx;
1292 cairo_matrix.yx = matrix.yx;
1293 cairo_matrix.xy = matrix.xy;
1294 cairo_matrix.yy = matrix.yy;
1295 cairo_matrix.x0 = matrix.x0;
1296 cairo_matrix.y0 = matrix.y0;
1297 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1303 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1306 /* reallocate overlay buffer */
1307 buffer = gst_buffer_new_and_alloc (4 * width * height);
1308 gst_buffer_replace (&overlay->text_image, buffer);
1309 gst_buffer_unref (buffer);
1311 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1312 surface = cairo_image_surface_create_for_data (map.data,
1313 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1314 cr = cairo_create (surface);
1317 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1320 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1322 if (overlay->want_shading)
1323 cairo_paint_with_alpha (cr, overlay->shading_value);
1325 /* apply transformations */
1326 cairo_set_matrix (cr, &cairo_matrix);
1328 /* FIXME: We use show_layout everywhere except for the surface
1329 * because it's really faster and internally does all kinds of
1330 * caching. Unfortunately we have to paint to a cairo path for
1331 * the outline and this is slow. Once Pango supports user fonts
1332 * we should use them, see
1333 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1335 * Idea would the be, to create a cairo user font that
1336 * does shadow, outline, text painting in the
1337 * render_glyph function.
1340 /* draw shadow text */
1342 PangoAttrList *origin_attr, *filtered_attr, *temp_attr;
1344 /* Store a ref on the original attributes for later restoration */
1346 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1347 /* Take a copy of the original attributes, because pango_attr_list_filter
1348 * modifies the passed list */
1349 temp_attr = pango_attr_list_copy (origin_attr);
1351 pango_attr_list_filter (temp_attr,
1352 gst_text_overlay_filter_foreground_attr, NULL);
1353 pango_attr_list_unref (temp_attr);
1356 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1357 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1358 pango_layout_set_attributes (overlay->layout, filtered_attr);
1359 pango_cairo_show_layout (cr, overlay->layout);
1360 pango_layout_set_attributes (overlay->layout, origin_attr);
1361 pango_attr_list_unref (filtered_attr);
1362 pango_attr_list_unref (origin_attr);
1366 a = (overlay->outline_color >> 24) & 0xff;
1367 r = (overlay->outline_color >> 16) & 0xff;
1368 g = (overlay->outline_color >> 8) & 0xff;
1369 b = (overlay->outline_color >> 0) & 0xff;
1371 /* draw outline text */
1373 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1374 cairo_set_line_width (cr, overlay->outline_offset);
1375 pango_cairo_layout_path (cr, overlay->layout);
1379 a = (overlay->color >> 24) & 0xff;
1380 r = (overlay->color >> 16) & 0xff;
1381 g = (overlay->color >> 8) & 0xff;
1382 b = (overlay->color >> 0) & 0xff;
1386 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1387 pango_cairo_show_layout (cr, overlay->layout);
1391 cairo_surface_destroy (surface);
1392 gst_buffer_unmap (buffer, &map);
1393 overlay->image_width = width;
1394 overlay->image_height = height;
1395 overlay->baseline_y = ink_rect.y;
1396 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1398 gst_base_text_overlay_set_composition (overlay);
1402 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1403 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1405 gint i, j, dest_stride;
1408 dest_stride = dest->info.stride[0];
1409 dest_ptr = dest->data[0];
1411 for (i = y0; i < y1; ++i) {
1412 for (j = x0; j < x1; ++j) {
1413 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1415 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1421 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1422 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1425 guint dest_stride, pixel_stride;
1428 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1429 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1430 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1433 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1435 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1438 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1440 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1442 for (i = y0; i < y1; i++) {
1443 for (j = x0; j < x1; j++) {
1447 y_pos = (i * dest_stride) + j * pixel_stride;
1448 y = dest_ptr[y_pos] + overlay->shading_value;
1450 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1455 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1456 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1457 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1459 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1460 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1465 dest_ptr = dest->data[0];
1467 for (i = y0; i < y1; i++) {
1468 for (j = x0; j < x1; j++) {
1471 y_pos = (i * 4 * overlay->width) + j * 4;
1472 for (k = 0; k < 4; k++) {
1473 y = dest_ptr[y_pos + k] + overlay->shading_value;
1474 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1482 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
1483 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1485 const int pstride = 3;
1486 gint y, x, stride, shading_val, tmp;
1489 shading_val = overlay->shading_value;
1490 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1492 for (y = y0; y < y1; ++y) {
1493 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1494 p += (y * stride) + (x0 * pstride);
1495 for (x = x0; x < x1; ++x) {
1496 tmp = *p + shading_val;
1497 *p++ = CLAMP (tmp, 0, 255);
1498 tmp = *p + shading_val;
1499 *p++ = CLAMP (tmp, 0, 255);
1500 tmp = *p + shading_val;
1501 *p++ = CLAMP (tmp, 0, 255);
1507 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
1508 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1510 gint y, x, stride, shading_val, tmp;
1513 shading_val = overlay->shading_value;
1514 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1516 /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
1517 for (y = y0; y < y1; ++y) {
1518 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1519 /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
1520 /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
1521 * unlikely anyone will notice.. */
1522 p += (y * stride) + ((x0 / 2) * 3) + 1;
1523 for (x = x0; x < x1; x += 2) {
1524 tmp = *p + shading_val;
1525 *p++ = CLAMP (tmp, 0, 255);
1526 tmp = *p + shading_val;
1527 *p++ = CLAMP (tmp, 0, 255);
1534 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1535 static inline void \
1536 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1537 gint x0, gint x1, gint y0, gint y1) \
1542 dest_ptr = dest->data[0];\
1544 for (i = y0; i < y1; i++) {\
1545 for (j = x0; j < x1; j++) {\
1547 y_pos = (i * 4 * overlay->width) + j * 4;\
1548 for (k = OFFSET; k < 3+OFFSET; k++) {\
1549 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1550 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1555 ARGB_SHADE_FUNCTION (ARGB, 1);
1556 ARGB_SHADE_FUNCTION (ABGR, 1);
1557 ARGB_SHADE_FUNCTION (RGBA, 0);
1558 ARGB_SHADE_FUNCTION (BGRA, 0);
1561 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1562 const gchar * text, gint textlen)
1566 if (!overlay->need_render) {
1567 GST_DEBUG ("Using previously rendered text.");
1571 /* -1 is the whole string */
1572 if (text != NULL && textlen < 0) {
1573 textlen = strlen (text);
1577 string = g_strndup (text, textlen);
1578 } else { /* empty string */
1579 string = g_strdup (" ");
1581 g_strdelimit (string, "\r\t", ' ');
1582 textlen = strlen (string);
1584 /* FIXME: should we check for UTF-8 here? */
1586 GST_DEBUG ("Rendering '%s'", string);
1587 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1591 overlay->need_render = FALSE;
1594 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
1599 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
1600 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1602 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1603 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1605 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1606 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1608 switch (overlay->format) {
1609 case GST_VIDEO_FORMAT_I420:
1610 case GST_VIDEO_FORMAT_YV12:
1611 case GST_VIDEO_FORMAT_NV12:
1612 case GST_VIDEO_FORMAT_NV21:
1613 case GST_VIDEO_FORMAT_Y41B:
1614 case GST_VIDEO_FORMAT_Y42B:
1615 case GST_VIDEO_FORMAT_Y444:
1616 case GST_VIDEO_FORMAT_YUV9:
1617 case GST_VIDEO_FORMAT_YVU9:
1618 case GST_VIDEO_FORMAT_GRAY8:
1619 case GST_VIDEO_FORMAT_A420:
1620 gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
1622 case GST_VIDEO_FORMAT_AYUV:
1623 case GST_VIDEO_FORMAT_UYVY:
1624 case GST_VIDEO_FORMAT_YUY2:
1625 case GST_VIDEO_FORMAT_v308:
1626 gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
1628 case GST_VIDEO_FORMAT_xRGB:
1629 gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
1631 case GST_VIDEO_FORMAT_xBGR:
1632 gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
1634 case GST_VIDEO_FORMAT_BGRx:
1635 gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
1637 case GST_VIDEO_FORMAT_RGBx:
1638 gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
1640 case GST_VIDEO_FORMAT_ARGB:
1641 gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
1643 case GST_VIDEO_FORMAT_ABGR:
1644 gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
1646 case GST_VIDEO_FORMAT_RGBA:
1647 gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
1649 case GST_VIDEO_FORMAT_BGRA:
1650 gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
1652 case GST_VIDEO_FORMAT_BGR:
1653 case GST_VIDEO_FORMAT_RGB:
1654 gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
1656 case GST_VIDEO_FORMAT_IYU1:
1657 gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
1660 GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
1661 gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
1666 static GstFlowReturn
1667 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1668 GstBuffer * video_frame)
1670 GstVideoFrame frame;
1672 if (overlay->composition == NULL)
1675 if (gst_pad_check_reconfigure (overlay->srcpad))
1676 gst_base_text_overlay_negotiate (overlay);
1678 video_frame = gst_buffer_make_writable (video_frame);
1680 if (overlay->attach_compo_to_buffer) {
1681 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1682 gst_buffer_add_video_overlay_composition_meta (video_frame,
1683 overlay->composition);
1684 /* FIXME: emulate shaded background box if want_shading=true */
1688 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1692 /* shaded background box */
1693 if (overlay->want_shading) {
1696 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1698 gst_base_text_overlay_shade_background (overlay, &frame,
1699 xpos, xpos + overlay->image_width, ypos, ypos + overlay->image_height);
1702 gst_video_overlay_composition_blend (overlay->composition, &frame);
1704 gst_video_frame_unmap (&frame);
1708 return gst_pad_push (overlay->srcpad, video_frame);
1713 gst_buffer_unref (video_frame);
1714 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1719 static GstPadLinkReturn
1720 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
1723 GstBaseTextOverlay *overlay;
1725 overlay = GST_BASE_TEXT_OVERLAY (parent);
1726 if (G_UNLIKELY (!overlay))
1727 return GST_PAD_LINK_REFUSED;
1729 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1731 overlay->text_linked = TRUE;
1733 return GST_PAD_LINK_OK;
1737 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
1739 GstBaseTextOverlay *overlay;
1741 /* don't use gst_pad_get_parent() here, will deadlock */
1742 overlay = GST_BASE_TEXT_OVERLAY (parent);
1744 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1746 overlay->text_linked = FALSE;
1748 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1752 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1755 gboolean ret = FALSE;
1756 GstBaseTextOverlay *overlay = NULL;
1758 overlay = GST_BASE_TEXT_OVERLAY (parent);
1760 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1762 switch (GST_EVENT_TYPE (event)) {
1763 case GST_EVENT_CAPS:
1767 gst_event_parse_caps (event, &caps);
1768 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1769 gst_event_unref (event);
1772 case GST_EVENT_SEGMENT:
1774 const GstSegment *segment;
1776 overlay->text_eos = FALSE;
1778 gst_event_parse_segment (event, &segment);
1780 if (segment->format == GST_FORMAT_TIME) {
1781 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1782 gst_segment_copy_into (segment, &overlay->text_segment);
1783 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1784 &overlay->text_segment);
1785 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1787 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1788 ("received non-TIME newsegment event on text input"));
1791 gst_event_unref (event);
1794 /* wake up the video chain, it might be waiting for a text buffer or
1795 * a text segment update */
1796 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1797 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1798 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1803 GstClockTime start, duration;
1805 gst_event_parse_gap (event, &start, &duration);
1806 if (GST_CLOCK_TIME_IS_VALID (duration))
1808 /* we do not expect another buffer until after gap,
1809 * so that is our position now */
1810 overlay->text_segment.position = start;
1812 /* wake up the video chain, it might be waiting for a text buffer or
1813 * a text segment update */
1814 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1815 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1816 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1819 case GST_EVENT_FLUSH_STOP:
1820 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1821 GST_INFO_OBJECT (overlay, "text flush stop");
1822 overlay->text_flushing = FALSE;
1823 overlay->text_eos = FALSE;
1824 gst_base_text_overlay_pop_text (overlay);
1825 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1826 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1827 gst_event_unref (event);
1830 case GST_EVENT_FLUSH_START:
1831 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1832 GST_INFO_OBJECT (overlay, "text flush start");
1833 overlay->text_flushing = TRUE;
1834 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1835 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1836 gst_event_unref (event);
1840 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1841 overlay->text_eos = TRUE;
1842 GST_INFO_OBJECT (overlay, "text EOS");
1843 /* wake up the video chain, it might be waiting for a text buffer or
1844 * a text segment update */
1845 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1846 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1847 gst_event_unref (event);
1851 ret = gst_pad_event_default (pad, parent, event);
1859 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1862 gboolean ret = FALSE;
1863 GstBaseTextOverlay *overlay = NULL;
1865 overlay = GST_BASE_TEXT_OVERLAY (parent);
1867 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1869 switch (GST_EVENT_TYPE (event)) {
1870 case GST_EVENT_CAPS:
1874 gst_event_parse_caps (event, &caps);
1875 ret = gst_base_text_overlay_setcaps (overlay, caps);
1876 gst_event_unref (event);
1879 case GST_EVENT_SEGMENT:
1881 const GstSegment *segment;
1883 GST_DEBUG_OBJECT (overlay, "received new segment");
1885 gst_event_parse_segment (event, &segment);
1887 if (segment->format == GST_FORMAT_TIME) {
1888 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1891 gst_segment_copy_into (segment, &overlay->segment);
1893 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1894 ("received non-TIME newsegment event on video input"));
1897 ret = gst_pad_event_default (pad, parent, event);
1901 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1902 GST_INFO_OBJECT (overlay, "video EOS");
1903 overlay->video_eos = TRUE;
1904 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1905 ret = gst_pad_event_default (pad, parent, event);
1907 case GST_EVENT_FLUSH_START:
1908 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1909 GST_INFO_OBJECT (overlay, "video flush start");
1910 overlay->video_flushing = TRUE;
1911 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1912 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1913 ret = gst_pad_event_default (pad, parent, event);
1915 case GST_EVENT_FLUSH_STOP:
1916 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1917 GST_INFO_OBJECT (overlay, "video flush stop");
1918 overlay->video_flushing = FALSE;
1919 overlay->video_eos = FALSE;
1920 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1921 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1922 ret = gst_pad_event_default (pad, parent, event);
1925 ret = gst_pad_event_default (pad, parent, event);
1933 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1936 gboolean ret = FALSE;
1937 GstBaseTextOverlay *overlay;
1939 overlay = GST_BASE_TEXT_OVERLAY (parent);
1941 switch (GST_QUERY_TYPE (query)) {
1942 case GST_QUERY_CAPS:
1944 GstCaps *filter, *caps;
1946 gst_query_parse_caps (query, &filter);
1947 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1948 gst_query_set_caps_result (query, caps);
1949 gst_caps_unref (caps);
1954 ret = gst_pad_query_default (pad, parent, query);
1961 /* Called with lock held */
1963 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1965 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1967 if (overlay->text_buffer) {
1968 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1969 overlay->text_buffer);
1970 gst_buffer_unref (overlay->text_buffer);
1971 overlay->text_buffer = NULL;
1974 /* Let the text task know we used that buffer */
1975 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1978 /* We receive text buffers here. If they are out of segment we just ignore them.
1979 If the buffer is in our segment we keep it internally except if another one
1980 is already waiting here, in that case we wait that it gets kicked out */
1981 static GstFlowReturn
1982 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1985 GstFlowReturn ret = GST_FLOW_OK;
1986 GstBaseTextOverlay *overlay = NULL;
1987 gboolean in_seg = FALSE;
1988 guint64 clip_start = 0, clip_stop = 0;
1990 overlay = GST_BASE_TEXT_OVERLAY (parent);
1992 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1994 if (overlay->text_flushing) {
1995 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1996 ret = GST_FLOW_FLUSHING;
1997 GST_LOG_OBJECT (overlay, "text flushing");
2001 if (overlay->text_eos) {
2002 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2004 GST_LOG_OBJECT (overlay, "text EOS");
2008 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2009 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2010 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2011 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2012 GST_BUFFER_DURATION (buffer)));
2014 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2017 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2018 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2020 stop = GST_CLOCK_TIME_NONE;
2022 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2023 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2029 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2030 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2031 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2032 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2034 /* Wait for the previous buffer to go away */
2035 while (overlay->text_buffer != NULL) {
2036 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2037 GST_DEBUG_PAD_NAME (pad));
2038 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2039 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2040 if (overlay->text_flushing) {
2041 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2042 ret = GST_FLOW_FLUSHING;
2047 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2048 overlay->text_segment.position = clip_start;
2050 overlay->text_buffer = buffer;
2051 /* That's a new text buffer we need to render */
2052 overlay->need_render = TRUE;
2054 /* in case the video chain is waiting for a text buffer, wake it up */
2055 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2058 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2065 static GstFlowReturn
2066 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2069 GstBaseTextOverlayClass *klass;
2070 GstBaseTextOverlay *overlay;
2071 GstFlowReturn ret = GST_FLOW_OK;
2072 gboolean in_seg = FALSE;
2073 guint64 start, stop, clip_start = 0, clip_stop = 0;
2076 overlay = GST_BASE_TEXT_OVERLAY (parent);
2077 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2079 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2080 goto missing_timestamp;
2082 /* ignore buffers that are outside of the current segment */
2083 start = GST_BUFFER_TIMESTAMP (buffer);
2085 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2086 stop = GST_CLOCK_TIME_NONE;
2088 stop = start + GST_BUFFER_DURATION (buffer);
2091 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2092 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2093 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2095 /* segment_clip() will adjust start unconditionally to segment_start if
2096 * no stop time is provided, so handle this ourselves */
2097 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2098 goto out_of_segment;
2100 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2101 &clip_start, &clip_stop);
2104 goto out_of_segment;
2106 /* if the buffer is only partially in the segment, fix up stamps */
2107 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2108 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2109 buffer = gst_buffer_make_writable (buffer);
2110 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2112 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2115 /* now, after we've done the clipping, fix up end time if there's no
2116 * duration (we only use those estimated values internally though, we
2117 * don't want to set bogus values on the buffer itself) */
2121 gint fps_num, fps_denom;
2123 /* FIXME, store this in setcaps */
2124 caps = gst_pad_get_current_caps (pad);
2125 s = gst_caps_get_structure (caps, 0);
2126 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2127 fps_num && fps_denom) {
2128 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2129 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2131 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2132 stop = start + 1; /* we need to assume some interval */
2134 gst_caps_unref (caps);
2137 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2141 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2143 if (overlay->video_flushing)
2146 if (overlay->video_eos)
2149 if (overlay->silent) {
2150 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2151 ret = gst_pad_push (overlay->srcpad, buffer);
2153 /* Update position */
2154 overlay->segment.position = clip_start;
2159 /* Text pad not linked, rendering internal text */
2160 if (!overlay->text_linked) {
2161 if (klass->get_text) {
2162 text = klass->get_text (overlay, buffer);
2164 text = g_strdup (overlay->default_text);
2167 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2168 "text: '%s'", GST_STR_NULL (text));
2170 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2172 if (text != NULL && *text != '\0') {
2173 /* Render and push */
2174 gst_base_text_overlay_render_text (overlay, text, -1);
2175 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2177 /* Invalid or empty string */
2178 ret = gst_pad_push (overlay->srcpad, buffer);
2181 /* Text pad linked, check if we have a text buffer queued */
2182 if (overlay->text_buffer) {
2183 gboolean pop_text = FALSE, valid_text_time = TRUE;
2184 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2185 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2186 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2187 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2188 GstClockTime vid_running_time, vid_running_time_end;
2190 /* if the text buffer isn't stamped right, pop it off the
2191 * queue and display it for the current video frame only */
2192 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2193 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2194 GST_WARNING_OBJECT (overlay,
2195 "Got text buffer with invalid timestamp or duration");
2197 valid_text_time = FALSE;
2199 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2200 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2204 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2206 vid_running_time_end =
2207 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2210 /* If timestamp and duration are valid */
2211 if (valid_text_time) {
2213 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2215 text_running_time_end =
2216 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2220 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2221 GST_TIME_ARGS (text_running_time),
2222 GST_TIME_ARGS (text_running_time_end));
2223 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2224 GST_TIME_ARGS (vid_running_time),
2225 GST_TIME_ARGS (vid_running_time_end));
2227 /* Text too old or in the future */
2228 if (valid_text_time && text_running_time_end <= vid_running_time) {
2229 /* text buffer too old, get rid of it and do nothing */
2230 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2232 gst_base_text_overlay_pop_text (overlay);
2233 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2234 goto wait_for_text_buf;
2235 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2236 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2237 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2238 /* Push the video frame */
2239 ret = gst_pad_push (overlay->srcpad, buffer);
2245 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2246 in_text = (gchar *) map.data;
2250 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2251 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2252 * here on purpose, this is something that needs fixing upstream */
2253 if (!g_utf8_validate (in_text, in_size, NULL)) {
2254 const gchar *end = NULL;
2256 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2257 in_text = g_strndup (in_text, in_size);
2258 while (!g_utf8_validate (in_text, in_size, &end) && end)
2259 *((gchar *) end) = '*';
2262 /* Get the string */
2263 if (overlay->have_pango_markup) {
2264 text = g_strndup (in_text, in_size);
2266 text = g_markup_escape_text (in_text, in_size);
2269 if (text != NULL && *text != '\0') {
2270 gint text_len = strlen (text);
2272 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2273 text[text_len - 1] == '\r')) {
2276 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2277 gst_base_text_overlay_render_text (overlay, text, text_len);
2279 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2280 gst_base_text_overlay_render_text (overlay, " ", 1);
2282 if (in_text != (gchar *) map.data)
2285 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2286 gst_base_text_overlay_render_text (overlay, " ", 1);
2289 gst_buffer_unmap (overlay->text_buffer, &map);
2291 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2292 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2294 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2295 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2300 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2301 gst_base_text_overlay_pop_text (overlay);
2302 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2305 gboolean wait_for_text_buf = TRUE;
2307 if (overlay->text_eos)
2308 wait_for_text_buf = FALSE;
2310 if (!overlay->wait_text)
2311 wait_for_text_buf = FALSE;
2313 /* Text pad linked, but no text buffer available - what now? */
2314 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2315 GstClockTime text_start_running_time, text_position_running_time;
2316 GstClockTime vid_running_time;
2319 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2320 GST_BUFFER_TIMESTAMP (buffer));
2321 text_start_running_time =
2322 gst_segment_to_running_time (&overlay->text_segment,
2323 GST_FORMAT_TIME, overlay->text_segment.start);
2324 text_position_running_time =
2325 gst_segment_to_running_time (&overlay->text_segment,
2326 GST_FORMAT_TIME, overlay->text_segment.position);
2328 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2329 vid_running_time < text_start_running_time) ||
2330 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2331 vid_running_time < text_position_running_time)) {
2332 wait_for_text_buf = FALSE;
2336 if (wait_for_text_buf) {
2337 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2338 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2339 GST_DEBUG_OBJECT (overlay, "resuming");
2340 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2341 goto wait_for_text_buf;
2343 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2344 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2345 ret = gst_pad_push (overlay->srcpad, buffer);
2352 /* Update position */
2353 overlay->segment.position = clip_start;
2359 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2360 gst_buffer_unref (buffer);
2366 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2367 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2368 gst_buffer_unref (buffer);
2369 return GST_FLOW_FLUSHING;
2373 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2374 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2375 gst_buffer_unref (buffer);
2376 return GST_FLOW_EOS;
2380 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2381 gst_buffer_unref (buffer);
2386 static GstStateChangeReturn
2387 gst_base_text_overlay_change_state (GstElement * element,
2388 GstStateChange transition)
2390 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2391 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2393 switch (transition) {
2394 case GST_STATE_CHANGE_PAUSED_TO_READY:
2395 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2396 overlay->text_flushing = TRUE;
2397 overlay->video_flushing = TRUE;
2398 /* pop_text will broadcast on the GCond and thus also make the video
2399 * chain exit if it's waiting for a text buffer */
2400 gst_base_text_overlay_pop_text (overlay);
2401 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2407 ret = parent_class->change_state (element, transition);
2408 if (ret == GST_STATE_CHANGE_FAILURE)
2411 switch (transition) {
2412 case GST_STATE_CHANGE_READY_TO_PAUSED:
2413 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2414 overlay->text_flushing = FALSE;
2415 overlay->video_flushing = FALSE;
2416 overlay->video_eos = FALSE;
2417 overlay->text_eos = FALSE;
2418 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2419 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2420 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2430 plugin_init (GstPlugin * plugin)
2432 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2433 GST_TYPE_TEXT_OVERLAY) ||
2434 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2435 GST_TYPE_TIME_OVERLAY) ||
2436 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2437 GST_TYPE_CLOCK_OVERLAY) ||
2438 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2439 GST_TYPE_TEXT_RENDER)) {
2443 /*texttestsrc_plugin_init(module, plugin); */
2445 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2450 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2451 pango, "Pango-based text rendering and overlay", plugin_init,
2452 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)