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
124 #define COMP_Y(ret, r, g, b) \
126 ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
127 ret = CLAMP (ret, 0, 255); \
130 #define COMP_U(ret, r, g, b) \
132 ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
133 ret = CLAMP (ret, 0, 255); \
136 #define COMP_V(ret, r, g, b) \
138 ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
139 ret = CLAMP (ret, 0, 255); \
142 #define BLEND(ret, alpha, v0, v1) \
144 ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
147 #define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew) \
150 _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \
151 ret = CLAMP (_tmp, 0, 255); \
154 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
155 # define CAIRO_ARGB_A 3
156 # define CAIRO_ARGB_R 2
157 # define CAIRO_ARGB_G 1
158 # define CAIRO_ARGB_B 0
160 # define CAIRO_ARGB_A 0
161 # define CAIRO_ARGB_R 1
162 # define CAIRO_ARGB_G 2
163 # define CAIRO_ARGB_B 3
184 PROP_AUTO_ADJUST_SIZE,
185 PROP_VERTICAL_RENDER,
192 /* FIXME: video-blend.c doesn't support formats with more than 8 bit per
193 * component (which get unpacked into ARGB64 or AYUV64) yet, such as:
194 * v210, v216, UYVP, GRAY16_LE, GRAY16_BE */
195 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
196 I420, YV12, AYUV, YUY2, UYVY, v308, Y41B, Y42B, Y444, \
197 NV12, NV21, A420, YUV9, YVU9, IYU1, GRAY8 }"
199 static GstStaticPadTemplate src_template_factory =
200 GST_STATIC_PAD_TEMPLATE ("src",
203 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
206 static GstStaticPadTemplate video_sink_template_factory =
207 GST_STATIC_PAD_TEMPLATE ("video_sink",
210 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
213 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
215 gst_base_text_overlay_valign_get_type (void)
217 static GType base_text_overlay_valign_type = 0;
218 static const GEnumValue base_text_overlay_valign[] = {
219 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
220 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
221 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
222 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
223 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
227 if (!base_text_overlay_valign_type) {
228 base_text_overlay_valign_type =
229 g_enum_register_static ("GstBaseTextOverlayVAlign",
230 base_text_overlay_valign);
232 return base_text_overlay_valign_type;
235 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
237 gst_base_text_overlay_halign_get_type (void)
239 static GType base_text_overlay_halign_type = 0;
240 static const GEnumValue base_text_overlay_halign[] = {
241 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
242 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
243 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
244 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
248 if (!base_text_overlay_halign_type) {
249 base_text_overlay_halign_type =
250 g_enum_register_static ("GstBaseTextOverlayHAlign",
251 base_text_overlay_halign);
253 return base_text_overlay_halign_type;
257 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
259 gst_base_text_overlay_wrap_mode_get_type (void)
261 static GType base_text_overlay_wrap_mode_type = 0;
262 static const GEnumValue base_text_overlay_wrap_mode[] = {
263 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
264 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
265 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
266 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
270 if (!base_text_overlay_wrap_mode_type) {
271 base_text_overlay_wrap_mode_type =
272 g_enum_register_static ("GstBaseTextOverlayWrapMode",
273 base_text_overlay_wrap_mode);
275 return base_text_overlay_wrap_mode_type;
278 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
280 gst_base_text_overlay_line_align_get_type (void)
282 static GType base_text_overlay_line_align_type = 0;
283 static const GEnumValue base_text_overlay_line_align[] = {
284 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
285 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
286 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
290 if (!base_text_overlay_line_align_type) {
291 base_text_overlay_line_align_type =
292 g_enum_register_static ("GstBaseTextOverlayLineAlign",
293 base_text_overlay_line_align);
295 return base_text_overlay_line_align_type;
298 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
299 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
300 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
301 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
302 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
303 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
304 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
306 static GstElementClass *parent_class = NULL;
307 static void gst_base_text_overlay_base_init (gpointer g_class);
308 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
309 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
310 GstBaseTextOverlayClass * klass);
312 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
313 element, GstStateChange transition);
315 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
316 GstBaseTextOverlay * overlay, GstCaps * filter);
317 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
319 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
321 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
322 GstObject * parent, GstEvent * event);
323 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
324 GstObject * parent, GstQuery * query);
326 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
327 GstObject * parent, GstEvent * event);
328 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
329 GstObject * parent, GstQuery * query);
330 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
331 GstObject * parent, GstBuffer * buffer);
333 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
334 GstObject * parent, GstEvent * event);
335 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
336 GstObject * parent, GstBuffer * buffer);
337 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
338 GstObject * parent, GstPad * peer);
339 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
341 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
342 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
345 static void gst_base_text_overlay_finalize (GObject * object);
346 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
347 const GValue * value, GParamSpec * pspec);
348 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
349 GValue * value, GParamSpec * pspec);
351 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
352 PangoFontDescription * desc);
355 gst_base_text_overlay_get_type (void)
357 static GType type = 0;
359 if (g_once_init_enter ((gsize *) & type)) {
360 static const GTypeInfo info = {
361 sizeof (GstBaseTextOverlayClass),
362 (GBaseInitFunc) gst_base_text_overlay_base_init,
364 (GClassInitFunc) gst_base_text_overlay_class_init,
367 sizeof (GstBaseTextOverlay),
369 (GInstanceInitFunc) gst_base_text_overlay_init,
372 g_once_init_leave ((gsize *) & type,
373 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
381 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
382 GstBuffer * video_frame)
384 return g_strdup (overlay->default_text);
388 gst_base_text_overlay_base_init (gpointer g_class)
390 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
391 PangoFontMap *fontmap;
393 /* Only lock for the subclasses here, the base class
394 * doesn't have this mutex yet and it's not necessary
396 if (klass->pango_lock)
397 g_mutex_lock (klass->pango_lock);
398 fontmap = pango_cairo_font_map_get_default ();
399 klass->pango_context =
400 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
401 if (klass->pango_lock)
402 g_mutex_unlock (klass->pango_lock);
406 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
408 GObjectClass *gobject_class;
409 GstElementClass *gstelement_class;
411 gobject_class = (GObjectClass *) klass;
412 gstelement_class = (GstElementClass *) klass;
414 parent_class = g_type_class_peek_parent (klass);
416 gobject_class->finalize = gst_base_text_overlay_finalize;
417 gobject_class->set_property = gst_base_text_overlay_set_property;
418 gobject_class->get_property = gst_base_text_overlay_get_property;
420 gst_element_class_add_pad_template (gstelement_class,
421 gst_static_pad_template_get (&src_template_factory));
422 gst_element_class_add_pad_template (gstelement_class,
423 gst_static_pad_template_get (&video_sink_template_factory));
425 gstelement_class->change_state =
426 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
428 klass->pango_lock = g_slice_new (GMutex);
429 g_mutex_init (klass->pango_lock);
431 klass->get_text = gst_base_text_overlay_get_text;
433 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
434 g_param_spec_string ("text", "text",
435 "Text to be display.", DEFAULT_PROP_TEXT,
436 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
437 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
438 g_param_spec_boolean ("shaded-background", "shaded background",
439 "Whether to shade the background under the text area",
440 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
441 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
442 g_param_spec_enum ("valignment", "vertical alignment",
443 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
444 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
445 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
446 g_param_spec_enum ("halignment", "horizontal alignment",
447 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
448 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
449 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
450 g_param_spec_int ("xpad", "horizontal paddding",
451 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
452 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
453 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
454 g_param_spec_int ("ypad", "vertical padding",
455 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
456 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
457 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
458 g_param_spec_int ("deltax", "X position modifier",
459 "Shift X position to the left or to the right. Unit is pixels.",
460 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
461 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
462 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
463 g_param_spec_int ("deltay", "Y position modifier",
464 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
465 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
467 * GstBaseTextOverlay:xpos
469 * Horizontal position of the rendered text when using positioned alignment.
473 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
474 g_param_spec_double ("xpos", "horizontal position",
475 "Horizontal position when using position alignment", 0, 1.0,
477 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
479 * GstBaseTextOverlay:ypos
481 * Vertical position of the rendered text when using positioned alignment.
485 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
486 g_param_spec_double ("ypos", "vertical position",
487 "Vertical position when using position alignment", 0, 1.0,
489 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
490 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
491 g_param_spec_enum ("wrap-mode", "wrap mode",
492 "Whether to wrap the text and if so how.",
493 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
494 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
495 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
496 g_param_spec_string ("font-desc", "font description",
497 "Pango font description of font to be used for rendering. "
498 "See documentation of pango_font_description_from_string "
499 "for syntax.", DEFAULT_PROP_FONT_DESC,
500 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
502 * GstBaseTextOverlay:color
504 * Color of the rendered text.
508 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
509 g_param_spec_uint ("color", "Color",
510 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
512 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
514 * GstTextOverlay:outline-color
516 * Color of the outline of the rendered text.
520 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
521 g_param_spec_uint ("outline-color", "Text Outline Color",
522 "Color to use for outline the text (big-endian ARGB).", 0,
523 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
524 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
527 * GstBaseTextOverlay:line-alignment
529 * Alignment of text lines relative to each other (for multi-line text)
533 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
534 g_param_spec_enum ("line-alignment", "line alignment",
535 "Alignment of text lines relative to each other.",
536 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
537 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
539 * GstBaseTextOverlay:silent
541 * If set, no text is rendered. Useful to switch off text rendering
542 * temporarily without removing the textoverlay element from the pipeline.
546 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
547 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
548 g_param_spec_boolean ("silent", "silent",
549 "Whether to render the text string",
551 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
553 * GstBaseTextOverlay:wait-text
555 * If set, the video will block until a subtitle is received on the text pad.
556 * If video and subtitles are sent in sync, like from the same demuxer, this
557 * property should be set.
561 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
562 g_param_spec_boolean ("wait-text", "Wait Text",
563 "Whether to wait for subtitles",
564 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
566 g_object_class_install_property (G_OBJECT_CLASS (klass),
567 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
568 "Automatically adjust font size to screen-size.",
569 DEFAULT_PROP_AUTO_ADJUST_SIZE,
570 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
572 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
573 g_param_spec_boolean ("vertical-render", "vertical render",
574 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
575 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
579 gst_base_text_overlay_finalize (GObject * object)
581 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
583 g_free (overlay->default_text);
585 if (overlay->composition) {
586 gst_video_overlay_composition_unref (overlay->composition);
587 overlay->composition = NULL;
590 if (overlay->text_image) {
591 gst_buffer_unref (overlay->text_image);
592 overlay->text_image = NULL;
595 if (overlay->layout) {
596 g_object_unref (overlay->layout);
597 overlay->layout = NULL;
600 if (overlay->text_buffer) {
601 gst_buffer_unref (overlay->text_buffer);
602 overlay->text_buffer = NULL;
605 g_mutex_clear (&overlay->lock);
606 g_cond_clear (&overlay->cond);
608 G_OBJECT_CLASS (parent_class)->finalize (object);
612 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
613 GstBaseTextOverlayClass * klass)
615 GstPadTemplate *template;
616 PangoFontDescription *desc;
619 template = gst_static_pad_template_get (&video_sink_template_factory);
620 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
621 gst_object_unref (template);
622 gst_pad_set_event_function (overlay->video_sinkpad,
623 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
624 gst_pad_set_chain_function (overlay->video_sinkpad,
625 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
626 gst_pad_set_query_function (overlay->video_sinkpad,
627 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
628 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
629 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
632 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
636 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
638 gst_pad_set_event_function (overlay->text_sinkpad,
639 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
640 gst_pad_set_chain_function (overlay->text_sinkpad,
641 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
642 gst_pad_set_link_function (overlay->text_sinkpad,
643 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
644 gst_pad_set_unlink_function (overlay->text_sinkpad,
645 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
646 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
650 template = gst_static_pad_template_get (&src_template_factory);
651 overlay->srcpad = gst_pad_new_from_template (template, "src");
652 gst_object_unref (template);
653 gst_pad_set_event_function (overlay->srcpad,
654 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
655 gst_pad_set_query_function (overlay->srcpad,
656 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
657 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
659 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
660 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
662 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
663 (overlay)->pango_context);
665 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
666 (overlay)->pango_context);
667 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
669 overlay->color = DEFAULT_PROP_COLOR;
670 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
671 overlay->halign = DEFAULT_PROP_HALIGNMENT;
672 overlay->valign = DEFAULT_PROP_VALIGNMENT;
673 overlay->xpad = DEFAULT_PROP_XPAD;
674 overlay->ypad = DEFAULT_PROP_YPAD;
675 overlay->deltax = DEFAULT_PROP_DELTAX;
676 overlay->deltay = DEFAULT_PROP_DELTAY;
677 overlay->xpos = DEFAULT_PROP_XPOS;
678 overlay->ypos = DEFAULT_PROP_YPOS;
680 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
682 overlay->want_shading = DEFAULT_PROP_SHADING;
683 overlay->shading_value = DEFAULT_SHADING_VALUE;
684 overlay->silent = DEFAULT_PROP_SILENT;
685 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
686 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
688 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
689 overlay->need_render = TRUE;
690 overlay->text_image = NULL;
691 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
692 gst_base_text_overlay_update_render_mode (overlay);
694 overlay->text_buffer = NULL;
695 overlay->text_linked = FALSE;
696 g_mutex_init (&overlay->lock);
697 g_cond_init (&overlay->cond);
698 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
699 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
703 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
705 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
706 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
707 pango_layout_set_width (overlay->layout, -1);
711 if (overlay->auto_adjust_size) {
712 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
713 if (overlay->use_vertical_render) {
714 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
718 (overlay->use_vertical_render ? overlay->height : overlay->width) *
722 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
723 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
724 pango_layout_set_width (overlay->layout, width);
725 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
730 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
732 PangoMatrix matrix = PANGO_MATRIX_INIT;
733 PangoContext *context = pango_layout_get_context (overlay->layout);
735 if (overlay->use_vertical_render) {
736 pango_matrix_rotate (&matrix, -90);
737 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
738 pango_context_set_matrix (context, &matrix);
739 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
741 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
742 pango_context_set_matrix (context, &matrix);
743 pango_layout_set_alignment (overlay->layout,
744 (PangoAlignment) overlay->line_align);
749 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
751 GstStructure *structure;
754 structure = gst_caps_get_structure (caps, 0);
755 format = gst_structure_get_string (structure, "format");
756 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
761 /* FIXME: upstream nego (e.g. when the video window is resized) */
763 /* only negotiate/query video overlay composition support for now */
765 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
769 gboolean attach = FALSE;
771 GST_DEBUG_OBJECT (overlay, "performing negotiation");
773 target = gst_pad_get_current_caps (overlay->srcpad);
775 if (!target || gst_caps_is_empty (target))
778 /* find supported meta */
779 query = gst_query_new_allocation (target, TRUE);
781 if (!gst_pad_peer_query (overlay->srcpad, query)) {
782 /* no problem, we use the query defaults */
783 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
786 if (gst_query_find_allocation_meta (query,
787 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
790 overlay->attach_compo_to_buffer = attach;
792 gst_query_unref (query);
793 gst_caps_unref (target);
800 gst_caps_unref (target);
806 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
809 gboolean ret = FALSE;
811 if (!gst_video_info_from_caps (&info, caps))
814 overlay->info = info;
815 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
816 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
817 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
819 ret = gst_pad_set_caps (overlay->srcpad, caps);
822 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
823 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
824 gst_base_text_overlay_negotiate (overlay);
825 gst_base_text_overlay_update_wrap_mode (overlay);
826 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
827 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
835 GST_DEBUG_OBJECT (overlay, "could not parse caps");
841 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
842 const GValue * value, GParamSpec * pspec)
844 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
846 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
849 g_free (overlay->default_text);
850 overlay->default_text = g_value_dup_string (value);
851 overlay->need_render = TRUE;
854 overlay->want_shading = g_value_get_boolean (value);
857 overlay->xpad = g_value_get_int (value);
860 overlay->ypad = g_value_get_int (value);
863 overlay->deltax = g_value_get_int (value);
866 overlay->deltay = g_value_get_int (value);
869 overlay->xpos = g_value_get_double (value);
872 overlay->ypos = g_value_get_double (value);
874 case PROP_VALIGNMENT:
875 overlay->valign = g_value_get_enum (value);
877 case PROP_HALIGNMENT:
878 overlay->halign = g_value_get_enum (value);
881 overlay->wrap_mode = g_value_get_enum (value);
882 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
883 gst_base_text_overlay_update_wrap_mode (overlay);
884 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
888 PangoFontDescription *desc;
889 const gchar *fontdesc_str;
891 fontdesc_str = g_value_get_string (value);
892 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
893 desc = pango_font_description_from_string (fontdesc_str);
895 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
896 pango_layout_set_font_description (overlay->layout, desc);
897 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
898 pango_font_description_free (desc);
900 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
903 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
907 overlay->color = g_value_get_uint (value);
909 case PROP_OUTLINE_COLOR:
910 overlay->outline_color = g_value_get_uint (value);
913 overlay->silent = g_value_get_boolean (value);
915 case PROP_LINE_ALIGNMENT:
916 overlay->line_align = g_value_get_enum (value);
917 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
918 pango_layout_set_alignment (overlay->layout,
919 (PangoAlignment) overlay->line_align);
920 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
923 overlay->wait_text = g_value_get_boolean (value);
925 case PROP_AUTO_ADJUST_SIZE:
926 overlay->auto_adjust_size = g_value_get_boolean (value);
927 overlay->need_render = TRUE;
929 case PROP_VERTICAL_RENDER:
930 overlay->use_vertical_render = g_value_get_boolean (value);
931 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
932 gst_base_text_overlay_update_render_mode (overlay);
933 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
934 overlay->need_render = TRUE;
937 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
941 overlay->need_render = TRUE;
942 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
946 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
947 GValue * value, GParamSpec * pspec)
949 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
951 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
954 g_value_set_string (value, overlay->default_text);
957 g_value_set_boolean (value, overlay->want_shading);
960 g_value_set_int (value, overlay->xpad);
963 g_value_set_int (value, overlay->ypad);
966 g_value_set_int (value, overlay->deltax);
969 g_value_set_int (value, overlay->deltay);
972 g_value_set_double (value, overlay->xpos);
975 g_value_set_double (value, overlay->ypos);
977 case PROP_VALIGNMENT:
978 g_value_set_enum (value, overlay->valign);
980 case PROP_HALIGNMENT:
981 g_value_set_enum (value, overlay->halign);
984 g_value_set_enum (value, overlay->wrap_mode);
987 g_value_set_boolean (value, overlay->silent);
989 case PROP_LINE_ALIGNMENT:
990 g_value_set_enum (value, overlay->line_align);
993 g_value_set_boolean (value, overlay->wait_text);
995 case PROP_AUTO_ADJUST_SIZE:
996 g_value_set_boolean (value, overlay->auto_adjust_size);
998 case PROP_VERTICAL_RENDER:
999 g_value_set_boolean (value, overlay->use_vertical_render);
1002 g_value_set_uint (value, overlay->color);
1004 case PROP_OUTLINE_COLOR:
1005 g_value_set_uint (value, overlay->outline_color);
1008 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1012 overlay->need_render = TRUE;
1013 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1017 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1020 gboolean ret = FALSE;
1021 GstBaseTextOverlay *overlay;
1023 overlay = GST_BASE_TEXT_OVERLAY (parent);
1025 switch (GST_QUERY_TYPE (query)) {
1026 case GST_QUERY_CAPS:
1028 GstCaps *filter, *caps;
1030 gst_query_parse_caps (query, &filter);
1031 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1032 gst_query_set_caps_result (query, caps);
1033 gst_caps_unref (caps);
1038 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1046 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1049 gboolean ret = FALSE;
1050 GstBaseTextOverlay *overlay = NULL;
1052 overlay = GST_BASE_TEXT_OVERLAY (parent);
1054 switch (GST_EVENT_TYPE (event)) {
1055 case GST_EVENT_SEEK:{
1058 /* We don't handle seek if we have not text pad */
1059 if (!overlay->text_linked) {
1060 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1061 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1065 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1067 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1069 /* Flush downstream, only for flushing seek */
1070 if (flags & GST_SEEK_FLAG_FLUSH)
1071 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1073 /* Mark ourself as flushing, unblock chains */
1074 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1075 overlay->video_flushing = TRUE;
1076 overlay->text_flushing = TRUE;
1077 gst_base_text_overlay_pop_text (overlay);
1078 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1080 /* Seek on each sink pad */
1081 gst_event_ref (event);
1082 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1084 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1086 gst_event_unref (event);
1091 if (overlay->text_linked) {
1092 gst_event_ref (event);
1093 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1094 gst_pad_push_event (overlay->text_sinkpad, event);
1096 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1107 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1113 if (G_UNLIKELY (!overlay))
1114 return gst_pad_get_pad_template_caps (pad);
1116 if (pad == overlay->srcpad)
1117 otherpad = overlay->video_sinkpad;
1119 otherpad = overlay->srcpad;
1121 /* we can do what the peer can */
1122 caps = gst_pad_peer_query_caps (otherpad, filter);
1124 GstCaps *temp, *templ;
1126 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1128 /* filtered against our padtemplate */
1129 templ = gst_pad_get_pad_template_caps (otherpad);
1130 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1131 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1132 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1133 gst_caps_unref (caps);
1134 gst_caps_unref (templ);
1135 /* this is what we can do */
1138 /* no peer, our padtemplate is enough then */
1139 caps = gst_pad_get_pad_template_caps (pad);
1141 GstCaps *intersection;
1144 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1145 gst_caps_unref (caps);
1146 caps = intersection;
1150 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1156 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1157 PangoFontDescription * desc)
1159 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1160 overlay->shadow_offset = (double) (font_size) / 13.0;
1161 overlay->outline_offset = (double) (font_size) / 15.0;
1162 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1163 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1167 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1168 gint * xpos, gint * ypos)
1171 GstBaseTextOverlayVAlign valign;
1172 GstBaseTextOverlayHAlign halign;
1174 width = overlay->image_width;
1175 height = overlay->image_height;
1177 if (overlay->use_vertical_render)
1178 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1180 halign = overlay->halign;
1183 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1184 *xpos = overlay->xpad;
1186 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1187 *xpos = (overlay->width - width) / 2;
1189 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1190 *xpos = overlay->width - width - overlay->xpad;
1192 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1193 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1194 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1201 *xpos += overlay->deltax;
1203 if (overlay->use_vertical_render)
1204 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1206 valign = overlay->valign;
1209 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1210 *ypos = overlay->height - height - overlay->ypad;
1212 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1213 *ypos = overlay->height - (height + overlay->ypad);
1215 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1216 *ypos = overlay->ypad;
1218 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1219 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1220 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1222 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1223 *ypos = (overlay->height - height) / 2;
1226 *ypos = overlay->ypad;
1229 *ypos += overlay->deltay;
1233 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1236 GstVideoOverlayRectangle *rectangle;
1238 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1240 if (overlay->text_image) {
1241 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1242 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1243 overlay->image_width, overlay->image_height);
1244 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1245 xpos, ypos, overlay->image_width, overlay->image_height,
1246 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1248 if (overlay->composition)
1249 gst_video_overlay_composition_unref (overlay->composition);
1250 overlay->composition = gst_video_overlay_composition_new (rectangle);
1251 gst_video_overlay_rectangle_unref (rectangle);
1253 } else if (overlay->composition) {
1254 gst_video_overlay_composition_unref (overlay->composition);
1255 overlay->composition = NULL;
1260 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1262 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1270 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1271 const gchar * string, gint textlen)
1274 cairo_surface_t *surface;
1275 PangoRectangle ink_rect, logical_rect;
1276 cairo_matrix_t cairo_matrix;
1278 double scalef = 1.0;
1283 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1285 if (overlay->auto_adjust_size) {
1286 /* 640 pixel is default */
1287 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1289 pango_layout_set_width (overlay->layout, -1);
1290 /* set text on pango layout */
1291 pango_layout_set_markup (overlay->layout, string, textlen);
1293 /* get subtitle image size */
1294 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1296 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1298 if (width + overlay->deltax >
1299 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1301 * subtitle image width is larger then overlay width
1302 * so rearrange overlay wrap mode.
1304 gst_base_text_overlay_update_wrap_mode (overlay);
1305 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1306 width = overlay->width;
1310 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1311 if (height > overlay->height) {
1312 height = overlay->height;
1314 if (overlay->use_vertical_render) {
1315 PangoRectangle rect;
1316 PangoContext *context;
1317 PangoMatrix matrix = PANGO_MATRIX_INIT;
1320 context = pango_layout_get_context (overlay->layout);
1322 pango_matrix_rotate (&matrix, -90);
1324 rect.x = rect.y = 0;
1326 rect.height = height;
1327 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1328 matrix.x0 = -rect.x;
1329 matrix.y0 = -rect.y;
1331 pango_context_set_matrix (context, &matrix);
1333 cairo_matrix.xx = matrix.xx;
1334 cairo_matrix.yx = matrix.yx;
1335 cairo_matrix.xy = matrix.xy;
1336 cairo_matrix.yy = matrix.yy;
1337 cairo_matrix.x0 = matrix.x0;
1338 cairo_matrix.y0 = matrix.y0;
1339 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1345 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1348 /* reallocate overlay buffer */
1349 buffer = gst_buffer_new_and_alloc (4 * width * height);
1350 gst_buffer_replace (&overlay->text_image, buffer);
1351 gst_buffer_unref (buffer);
1353 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1354 surface = cairo_image_surface_create_for_data (map.data,
1355 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1356 cr = cairo_create (surface);
1359 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1362 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1364 if (overlay->want_shading)
1365 cairo_paint_with_alpha (cr, overlay->shading_value);
1367 /* apply transformations */
1368 cairo_set_matrix (cr, &cairo_matrix);
1370 /* FIXME: We use show_layout everywhere except for the surface
1371 * because it's really faster and internally does all kinds of
1372 * caching. Unfortunately we have to paint to a cairo path for
1373 * the outline and this is slow. Once Pango supports user fonts
1374 * we should use them, see
1375 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1377 * Idea would the be, to create a cairo user font that
1378 * does shadow, outline, text painting in the
1379 * render_glyph function.
1382 /* draw shadow text */
1384 PangoAttrList *origin_attr, *filtered_attr;
1387 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1389 pango_attr_list_filter (pango_attr_list_copy (origin_attr),
1390 gst_text_overlay_filter_foreground_attr, NULL);
1393 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1394 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1395 pango_layout_set_attributes (overlay->layout, filtered_attr);
1396 pango_cairo_show_layout (cr, overlay->layout);
1397 pango_layout_set_attributes (overlay->layout, origin_attr);
1398 pango_attr_list_unref (filtered_attr);
1399 pango_attr_list_unref (origin_attr);
1403 a = (overlay->outline_color >> 24) & 0xff;
1404 r = (overlay->outline_color >> 16) & 0xff;
1405 g = (overlay->outline_color >> 8) & 0xff;
1406 b = (overlay->outline_color >> 0) & 0xff;
1408 /* draw outline text */
1410 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1411 cairo_set_line_width (cr, overlay->outline_offset);
1412 pango_cairo_layout_path (cr, overlay->layout);
1416 a = (overlay->color >> 24) & 0xff;
1417 r = (overlay->color >> 16) & 0xff;
1418 g = (overlay->color >> 8) & 0xff;
1419 b = (overlay->color >> 0) & 0xff;
1423 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1424 pango_cairo_show_layout (cr, overlay->layout);
1428 cairo_surface_destroy (surface);
1429 gst_buffer_unmap (buffer, &map);
1430 overlay->image_width = width;
1431 overlay->image_height = height;
1432 overlay->baseline_y = ink_rect.y;
1433 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1435 gst_base_text_overlay_set_composition (overlay);
1439 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1440 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1442 gint i, j, dest_stride;
1445 dest_stride = dest->info.stride[0];
1446 dest_ptr = dest->data[0];
1448 for (i = y0; i < y1; ++i) {
1449 for (j = x0; j < x1; ++j) {
1450 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1452 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1458 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1459 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1462 guint dest_stride, pixel_stride;
1465 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1466 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1467 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1470 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1472 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1475 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1477 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1479 for (i = y0; i < y1; i++) {
1480 for (j = x0; j < x1; j++) {
1484 y_pos = (i * dest_stride) + j * pixel_stride;
1485 y = dest_ptr[y_pos] + overlay->shading_value;
1487 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1492 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1493 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1494 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1496 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1497 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1502 dest_ptr = dest->data[0];
1504 for (i = y0; i < y1; i++) {
1505 for (j = x0; j < x1; j++) {
1508 y_pos = (i * 4 * overlay->width) + j * 4;
1509 for (k = 0; k < 4; k++) {
1510 y = dest_ptr[y_pos + k] + overlay->shading_value;
1511 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1519 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
1520 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1522 const int pstride = 3;
1523 gint y, x, stride, shading_val, tmp;
1526 shading_val = overlay->shading_value;
1527 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1529 for (y = y0; y < y1; ++y) {
1530 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1531 p += (y * stride) + (x0 * pstride);
1532 for (x = x0; x < x1; ++x) {
1533 tmp = *p + shading_val;
1534 *p++ = CLAMP (tmp, 0, 255);
1535 tmp = *p + shading_val;
1536 *p++ = CLAMP (tmp, 0, 255);
1537 tmp = *p + shading_val;
1538 *p++ = CLAMP (tmp, 0, 255);
1544 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
1545 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1547 gint y, x, stride, shading_val, tmp;
1550 shading_val = overlay->shading_value;
1551 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1553 /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
1554 for (y = y0; y < y1; ++y) {
1555 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1556 /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
1557 /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
1558 * unlikely anyone will notice.. */
1559 p += (y * stride) + ((x0 / 2) * 3) + 1;
1560 for (x = x0; x < x1; x += 2) {
1561 tmp = *p + shading_val;
1562 *p++ = CLAMP (tmp, 0, 255);
1563 tmp = *p + shading_val;
1564 *p++ = CLAMP (tmp, 0, 255);
1571 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1572 static inline void \
1573 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1574 gint x0, gint x1, gint y0, gint y1) \
1579 dest_ptr = dest->data[0];\
1581 for (i = y0; i < y1; i++) {\
1582 for (j = x0; j < x1; j++) {\
1584 y_pos = (i * 4 * overlay->width) + j * 4;\
1585 for (k = OFFSET; k < 3+OFFSET; k++) {\
1586 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1587 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1592 ARGB_SHADE_FUNCTION (ARGB, 1);
1593 ARGB_SHADE_FUNCTION (ABGR, 1);
1594 ARGB_SHADE_FUNCTION (RGBA, 0);
1595 ARGB_SHADE_FUNCTION (BGRA, 0);
1598 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1599 const gchar * text, gint textlen)
1603 if (!overlay->need_render) {
1604 GST_DEBUG ("Using previously rendered text.");
1608 /* -1 is the whole string */
1609 if (text != NULL && textlen < 0) {
1610 textlen = strlen (text);
1614 string = g_strndup (text, textlen);
1615 } else { /* empty string */
1616 string = g_strdup (" ");
1618 g_strdelimit (string, "\r\t", ' ');
1619 textlen = strlen (string);
1621 /* FIXME: should we check for UTF-8 here? */
1623 GST_DEBUG ("Rendering '%s'", string);
1624 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1628 overlay->need_render = FALSE;
1631 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
1636 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
1637 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1639 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1640 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1642 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1643 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1645 switch (overlay->format) {
1646 case GST_VIDEO_FORMAT_I420:
1647 case GST_VIDEO_FORMAT_YV12:
1648 case GST_VIDEO_FORMAT_NV12:
1649 case GST_VIDEO_FORMAT_NV21:
1650 case GST_VIDEO_FORMAT_Y41B:
1651 case GST_VIDEO_FORMAT_Y42B:
1652 case GST_VIDEO_FORMAT_Y444:
1653 case GST_VIDEO_FORMAT_YUV9:
1654 case GST_VIDEO_FORMAT_YVU9:
1655 case GST_VIDEO_FORMAT_GRAY8:
1656 case GST_VIDEO_FORMAT_A420:
1657 gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
1659 case GST_VIDEO_FORMAT_AYUV:
1660 case GST_VIDEO_FORMAT_UYVY:
1661 case GST_VIDEO_FORMAT_YUY2:
1662 case GST_VIDEO_FORMAT_v308:
1663 gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
1665 case GST_VIDEO_FORMAT_xRGB:
1666 gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
1668 case GST_VIDEO_FORMAT_xBGR:
1669 gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
1671 case GST_VIDEO_FORMAT_BGRx:
1672 gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
1674 case GST_VIDEO_FORMAT_RGBx:
1675 gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
1677 case GST_VIDEO_FORMAT_ARGB:
1678 gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
1680 case GST_VIDEO_FORMAT_ABGR:
1681 gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
1683 case GST_VIDEO_FORMAT_RGBA:
1684 gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
1686 case GST_VIDEO_FORMAT_BGRA:
1687 gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
1689 case GST_VIDEO_FORMAT_BGR:
1690 case GST_VIDEO_FORMAT_RGB:
1691 gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
1693 case GST_VIDEO_FORMAT_IYU1:
1694 gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
1697 GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
1698 gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
1703 static GstFlowReturn
1704 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1705 GstBuffer * video_frame)
1707 GstVideoFrame frame;
1709 if (overlay->composition == NULL)
1712 if (gst_pad_check_reconfigure (overlay->srcpad))
1713 gst_base_text_overlay_negotiate (overlay);
1715 video_frame = gst_buffer_make_writable (video_frame);
1717 if (overlay->attach_compo_to_buffer) {
1718 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1719 gst_buffer_add_video_overlay_composition_meta (video_frame,
1720 overlay->composition);
1721 /* FIXME: emulate shaded background box if want_shading=true */
1725 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1729 /* shaded background box */
1730 if (overlay->want_shading) {
1733 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1735 gst_base_text_overlay_shade_background (overlay, &frame,
1736 xpos, xpos + overlay->image_width, ypos, ypos + overlay->image_height);
1739 gst_video_overlay_composition_blend (overlay->composition, &frame);
1741 gst_video_frame_unmap (&frame);
1745 return gst_pad_push (overlay->srcpad, video_frame);
1750 gst_buffer_unref (video_frame);
1751 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1756 static GstPadLinkReturn
1757 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
1760 GstBaseTextOverlay *overlay;
1762 overlay = GST_BASE_TEXT_OVERLAY (parent);
1763 if (G_UNLIKELY (!overlay))
1764 return GST_PAD_LINK_REFUSED;
1766 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1768 overlay->text_linked = TRUE;
1770 return GST_PAD_LINK_OK;
1774 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
1776 GstBaseTextOverlay *overlay;
1778 /* don't use gst_pad_get_parent() here, will deadlock */
1779 overlay = GST_BASE_TEXT_OVERLAY (parent);
1781 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1783 overlay->text_linked = FALSE;
1785 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1789 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1792 gboolean ret = FALSE;
1793 GstBaseTextOverlay *overlay = NULL;
1795 overlay = GST_BASE_TEXT_OVERLAY (parent);
1797 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1799 switch (GST_EVENT_TYPE (event)) {
1800 case GST_EVENT_CAPS:
1804 gst_event_parse_caps (event, &caps);
1805 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1806 gst_event_unref (event);
1809 case GST_EVENT_SEGMENT:
1811 const GstSegment *segment;
1813 overlay->text_eos = FALSE;
1815 gst_event_parse_segment (event, &segment);
1817 if (segment->format == GST_FORMAT_TIME) {
1818 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1819 gst_segment_copy_into (segment, &overlay->text_segment);
1820 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1821 &overlay->text_segment);
1822 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1824 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1825 ("received non-TIME newsegment event on text input"));
1828 gst_event_unref (event);
1831 /* wake up the video chain, it might be waiting for a text buffer or
1832 * a text segment update */
1833 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1834 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1835 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1840 GstClockTime start, duration;
1842 gst_event_parse_gap (event, &start, &duration);
1843 if (GST_CLOCK_TIME_IS_VALID (duration))
1845 /* we do not expect another buffer until after gap,
1846 * so that is our position now */
1847 overlay->text_segment.position = start;
1849 /* wake up the video chain, it might be waiting for a text buffer or
1850 * a text segment update */
1851 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1852 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1853 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1856 case GST_EVENT_FLUSH_STOP:
1857 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1858 GST_INFO_OBJECT (overlay, "text flush stop");
1859 overlay->text_flushing = FALSE;
1860 overlay->text_eos = FALSE;
1861 gst_base_text_overlay_pop_text (overlay);
1862 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1863 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1864 gst_event_unref (event);
1867 case GST_EVENT_FLUSH_START:
1868 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1869 GST_INFO_OBJECT (overlay, "text flush start");
1870 overlay->text_flushing = TRUE;
1871 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1872 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1873 gst_event_unref (event);
1877 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1878 overlay->text_eos = TRUE;
1879 GST_INFO_OBJECT (overlay, "text EOS");
1880 /* wake up the video chain, it might be waiting for a text buffer or
1881 * a text segment update */
1882 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1883 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1884 gst_event_unref (event);
1888 ret = gst_pad_event_default (pad, parent, event);
1896 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1899 gboolean ret = FALSE;
1900 GstBaseTextOverlay *overlay = NULL;
1902 overlay = GST_BASE_TEXT_OVERLAY (parent);
1904 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1906 switch (GST_EVENT_TYPE (event)) {
1907 case GST_EVENT_CAPS:
1911 gst_event_parse_caps (event, &caps);
1912 ret = gst_base_text_overlay_setcaps (overlay, caps);
1913 gst_event_unref (event);
1916 case GST_EVENT_SEGMENT:
1918 const GstSegment *segment;
1920 GST_DEBUG_OBJECT (overlay, "received new segment");
1922 gst_event_parse_segment (event, &segment);
1924 if (segment->format == GST_FORMAT_TIME) {
1925 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1928 gst_segment_copy_into (segment, &overlay->segment);
1930 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1931 ("received non-TIME newsegment event on video input"));
1934 ret = gst_pad_event_default (pad, parent, event);
1938 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1939 GST_INFO_OBJECT (overlay, "video EOS");
1940 overlay->video_eos = TRUE;
1941 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1942 ret = gst_pad_event_default (pad, parent, event);
1944 case GST_EVENT_FLUSH_START:
1945 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1946 GST_INFO_OBJECT (overlay, "video flush start");
1947 overlay->video_flushing = TRUE;
1948 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1949 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1950 ret = gst_pad_event_default (pad, parent, event);
1952 case GST_EVENT_FLUSH_STOP:
1953 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1954 GST_INFO_OBJECT (overlay, "video flush stop");
1955 overlay->video_flushing = FALSE;
1956 overlay->video_eos = FALSE;
1957 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1958 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1959 ret = gst_pad_event_default (pad, parent, event);
1962 ret = gst_pad_event_default (pad, parent, event);
1970 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1973 gboolean ret = FALSE;
1974 GstBaseTextOverlay *overlay;
1976 overlay = GST_BASE_TEXT_OVERLAY (parent);
1978 switch (GST_QUERY_TYPE (query)) {
1979 case GST_QUERY_CAPS:
1981 GstCaps *filter, *caps;
1983 gst_query_parse_caps (query, &filter);
1984 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1985 gst_query_set_caps_result (query, caps);
1986 gst_caps_unref (caps);
1991 ret = gst_pad_query_default (pad, parent, query);
1998 /* Called with lock held */
2000 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2002 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2004 if (overlay->text_buffer) {
2005 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2006 overlay->text_buffer);
2007 gst_buffer_unref (overlay->text_buffer);
2008 overlay->text_buffer = NULL;
2011 /* Let the text task know we used that buffer */
2012 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2015 /* We receive text buffers here. If they are out of segment we just ignore them.
2016 If the buffer is in our segment we keep it internally except if another one
2017 is already waiting here, in that case we wait that it gets kicked out */
2018 static GstFlowReturn
2019 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
2022 GstFlowReturn ret = GST_FLOW_OK;
2023 GstBaseTextOverlay *overlay = NULL;
2024 gboolean in_seg = FALSE;
2025 guint64 clip_start = 0, clip_stop = 0;
2027 overlay = GST_BASE_TEXT_OVERLAY (parent);
2029 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2031 if (overlay->text_flushing) {
2032 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2033 ret = GST_FLOW_FLUSHING;
2034 GST_LOG_OBJECT (overlay, "text flushing");
2038 if (overlay->text_eos) {
2039 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2041 GST_LOG_OBJECT (overlay, "text EOS");
2045 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2046 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2047 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2048 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2049 GST_BUFFER_DURATION (buffer)));
2051 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2054 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2055 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2057 stop = GST_CLOCK_TIME_NONE;
2059 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2060 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2066 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2067 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2068 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2069 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2071 /* Wait for the previous buffer to go away */
2072 while (overlay->text_buffer != NULL) {
2073 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2074 GST_DEBUG_PAD_NAME (pad));
2075 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2076 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2077 if (overlay->text_flushing) {
2078 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2079 ret = GST_FLOW_FLUSHING;
2084 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2085 overlay->text_segment.position = clip_start;
2087 overlay->text_buffer = buffer;
2088 /* That's a new text buffer we need to render */
2089 overlay->need_render = TRUE;
2091 /* in case the video chain is waiting for a text buffer, wake it up */
2092 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2095 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2102 static GstFlowReturn
2103 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2106 GstBaseTextOverlayClass *klass;
2107 GstBaseTextOverlay *overlay;
2108 GstFlowReturn ret = GST_FLOW_OK;
2109 gboolean in_seg = FALSE;
2110 guint64 start, stop, clip_start = 0, clip_stop = 0;
2113 overlay = GST_BASE_TEXT_OVERLAY (parent);
2114 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2116 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2117 goto missing_timestamp;
2119 /* ignore buffers that are outside of the current segment */
2120 start = GST_BUFFER_TIMESTAMP (buffer);
2122 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2123 stop = GST_CLOCK_TIME_NONE;
2125 stop = start + GST_BUFFER_DURATION (buffer);
2128 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2129 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2130 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2132 /* segment_clip() will adjust start unconditionally to segment_start if
2133 * no stop time is provided, so handle this ourselves */
2134 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2135 goto out_of_segment;
2137 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2138 &clip_start, &clip_stop);
2141 goto out_of_segment;
2143 /* if the buffer is only partially in the segment, fix up stamps */
2144 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2145 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2146 buffer = gst_buffer_make_writable (buffer);
2147 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2149 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2152 /* now, after we've done the clipping, fix up end time if there's no
2153 * duration (we only use those estimated values internally though, we
2154 * don't want to set bogus values on the buffer itself) */
2158 gint fps_num, fps_denom;
2160 /* FIXME, store this in setcaps */
2161 caps = gst_pad_get_current_caps (pad);
2162 s = gst_caps_get_structure (caps, 0);
2163 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2164 fps_num && fps_denom) {
2165 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2166 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2168 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2169 stop = start + 1; /* we need to assume some interval */
2171 gst_caps_unref (caps);
2174 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2178 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2180 if (overlay->video_flushing)
2183 if (overlay->video_eos)
2186 if (overlay->silent) {
2187 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2188 ret = gst_pad_push (overlay->srcpad, buffer);
2190 /* Update position */
2191 overlay->segment.position = clip_start;
2196 /* Text pad not linked, rendering internal text */
2197 if (!overlay->text_linked) {
2198 if (klass->get_text) {
2199 text = klass->get_text (overlay, buffer);
2201 text = g_strdup (overlay->default_text);
2204 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2205 "text: '%s'", GST_STR_NULL (text));
2207 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2209 if (text != NULL && *text != '\0') {
2210 /* Render and push */
2211 gst_base_text_overlay_render_text (overlay, text, -1);
2212 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2214 /* Invalid or empty string */
2215 ret = gst_pad_push (overlay->srcpad, buffer);
2218 /* Text pad linked, check if we have a text buffer queued */
2219 if (overlay->text_buffer) {
2220 gboolean pop_text = FALSE, valid_text_time = TRUE;
2221 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2222 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2223 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2224 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2225 GstClockTime vid_running_time, vid_running_time_end;
2227 /* if the text buffer isn't stamped right, pop it off the
2228 * queue and display it for the current video frame only */
2229 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2230 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2231 GST_WARNING_OBJECT (overlay,
2232 "Got text buffer with invalid timestamp or duration");
2234 valid_text_time = FALSE;
2236 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2237 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2241 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2243 vid_running_time_end =
2244 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2247 /* If timestamp and duration are valid */
2248 if (valid_text_time) {
2250 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2252 text_running_time_end =
2253 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2257 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2258 GST_TIME_ARGS (text_running_time),
2259 GST_TIME_ARGS (text_running_time_end));
2260 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2261 GST_TIME_ARGS (vid_running_time),
2262 GST_TIME_ARGS (vid_running_time_end));
2264 /* Text too old or in the future */
2265 if (valid_text_time && text_running_time_end <= vid_running_time) {
2266 /* text buffer too old, get rid of it and do nothing */
2267 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2269 gst_base_text_overlay_pop_text (overlay);
2270 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2271 goto wait_for_text_buf;
2272 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2273 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2274 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2275 /* Push the video frame */
2276 ret = gst_pad_push (overlay->srcpad, buffer);
2282 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2283 in_text = (gchar *) map.data;
2287 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2288 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2289 * here on purpose, this is something that needs fixing upstream */
2290 if (!g_utf8_validate (in_text, in_size, NULL)) {
2291 const gchar *end = NULL;
2293 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2294 in_text = g_strndup (in_text, in_size);
2295 while (!g_utf8_validate (in_text, in_size, &end) && end)
2296 *((gchar *) end) = '*';
2299 /* Get the string */
2300 if (overlay->have_pango_markup) {
2301 text = g_strndup (in_text, in_size);
2303 text = g_markup_escape_text (in_text, in_size);
2306 if (text != NULL && *text != '\0') {
2307 gint text_len = strlen (text);
2309 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2310 text[text_len - 1] == '\r')) {
2313 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2314 gst_base_text_overlay_render_text (overlay, text, text_len);
2316 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2317 gst_base_text_overlay_render_text (overlay, " ", 1);
2319 if (in_text != (gchar *) map.data)
2322 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2323 gst_base_text_overlay_render_text (overlay, " ", 1);
2326 gst_buffer_unmap (overlay->text_buffer, &map);
2328 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2329 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2331 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2332 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2337 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2338 gst_base_text_overlay_pop_text (overlay);
2339 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2342 gboolean wait_for_text_buf = TRUE;
2344 if (overlay->text_eos)
2345 wait_for_text_buf = FALSE;
2347 if (!overlay->wait_text)
2348 wait_for_text_buf = FALSE;
2350 /* Text pad linked, but no text buffer available - what now? */
2351 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2352 GstClockTime text_start_running_time, text_position_running_time;
2353 GstClockTime vid_running_time;
2356 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2357 GST_BUFFER_TIMESTAMP (buffer));
2358 text_start_running_time =
2359 gst_segment_to_running_time (&overlay->text_segment,
2360 GST_FORMAT_TIME, overlay->text_segment.start);
2361 text_position_running_time =
2362 gst_segment_to_running_time (&overlay->text_segment,
2363 GST_FORMAT_TIME, overlay->text_segment.position);
2365 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2366 vid_running_time < text_start_running_time) ||
2367 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2368 vid_running_time < text_position_running_time)) {
2369 wait_for_text_buf = FALSE;
2373 if (wait_for_text_buf) {
2374 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2375 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2376 GST_DEBUG_OBJECT (overlay, "resuming");
2377 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2378 goto wait_for_text_buf;
2380 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2381 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2382 ret = gst_pad_push (overlay->srcpad, buffer);
2389 /* Update position */
2390 overlay->segment.position = clip_start;
2396 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2397 gst_buffer_unref (buffer);
2403 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2404 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2405 gst_buffer_unref (buffer);
2406 return GST_FLOW_FLUSHING;
2410 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2411 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2412 gst_buffer_unref (buffer);
2413 return GST_FLOW_EOS;
2417 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2418 gst_buffer_unref (buffer);
2423 static GstStateChangeReturn
2424 gst_base_text_overlay_change_state (GstElement * element,
2425 GstStateChange transition)
2427 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2428 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2430 switch (transition) {
2431 case GST_STATE_CHANGE_PAUSED_TO_READY:
2432 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2433 overlay->text_flushing = TRUE;
2434 overlay->video_flushing = TRUE;
2435 /* pop_text will broadcast on the GCond and thus also make the video
2436 * chain exit if it's waiting for a text buffer */
2437 gst_base_text_overlay_pop_text (overlay);
2438 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2444 ret = parent_class->change_state (element, transition);
2445 if (ret == GST_STATE_CHANGE_FAILURE)
2448 switch (transition) {
2449 case GST_STATE_CHANGE_READY_TO_PAUSED:
2450 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2451 overlay->text_flushing = FALSE;
2452 overlay->video_flushing = FALSE;
2453 overlay->video_eos = FALSE;
2454 overlay->text_eos = FALSE;
2455 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2456 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2457 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2467 plugin_init (GstPlugin * plugin)
2469 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2470 GST_TYPE_TEXT_OVERLAY) ||
2471 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2472 GST_TYPE_TIME_OVERLAY) ||
2473 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2474 GST_TYPE_CLOCK_OVERLAY) ||
2475 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2476 GST_TYPE_TEXT_RENDER)) {
2480 /*texttestsrc_plugin_init(module, plugin); */
2482 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2487 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2488 pango, "Pango-based text rendering and overlay", plugin_init,
2489 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)