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 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
193 I420, YV12, AYUV, YUY2, UYVY, v308, v210, v216, Y41B, Y42B, Y444, \
194 Y800, Y16, NV12, NV21, UYVP, A420, YUV9, IYU1 }"
196 static GstStaticPadTemplate src_template_factory =
197 GST_STATIC_PAD_TEMPLATE ("src",
200 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
203 static GstStaticPadTemplate video_sink_template_factory =
204 GST_STATIC_PAD_TEMPLATE ("video_sink",
207 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
210 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
212 gst_base_text_overlay_valign_get_type (void)
214 static GType base_text_overlay_valign_type = 0;
215 static const GEnumValue base_text_overlay_valign[] = {
216 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
217 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
218 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
219 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
220 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
224 if (!base_text_overlay_valign_type) {
225 base_text_overlay_valign_type =
226 g_enum_register_static ("GstBaseTextOverlayVAlign",
227 base_text_overlay_valign);
229 return base_text_overlay_valign_type;
232 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
234 gst_base_text_overlay_halign_get_type (void)
236 static GType base_text_overlay_halign_type = 0;
237 static const GEnumValue base_text_overlay_halign[] = {
238 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
239 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
240 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
241 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
245 if (!base_text_overlay_halign_type) {
246 base_text_overlay_halign_type =
247 g_enum_register_static ("GstBaseTextOverlayHAlign",
248 base_text_overlay_halign);
250 return base_text_overlay_halign_type;
254 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
256 gst_base_text_overlay_wrap_mode_get_type (void)
258 static GType base_text_overlay_wrap_mode_type = 0;
259 static const GEnumValue base_text_overlay_wrap_mode[] = {
260 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
261 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
262 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
263 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
267 if (!base_text_overlay_wrap_mode_type) {
268 base_text_overlay_wrap_mode_type =
269 g_enum_register_static ("GstBaseTextOverlayWrapMode",
270 base_text_overlay_wrap_mode);
272 return base_text_overlay_wrap_mode_type;
275 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
277 gst_base_text_overlay_line_align_get_type (void)
279 static GType base_text_overlay_line_align_type = 0;
280 static const GEnumValue base_text_overlay_line_align[] = {
281 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
282 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
283 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
287 if (!base_text_overlay_line_align_type) {
288 base_text_overlay_line_align_type =
289 g_enum_register_static ("GstBaseTextOverlayLineAlign",
290 base_text_overlay_line_align);
292 return base_text_overlay_line_align_type;
295 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
296 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
297 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
298 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
299 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
300 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
301 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
303 static GstElementClass *parent_class = NULL;
304 static void gst_base_text_overlay_base_init (gpointer g_class);
305 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
306 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
307 GstBaseTextOverlayClass * klass);
309 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
310 element, GstStateChange transition);
312 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
313 GstBaseTextOverlay * overlay, GstCaps * filter);
314 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
316 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
318 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
319 GstObject * parent, GstEvent * event);
320 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
321 GstObject * parent, GstQuery * query);
323 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
324 GstObject * parent, GstEvent * event);
325 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
326 GstObject * parent, GstQuery * query);
327 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
328 GstObject * parent, GstBuffer * buffer);
330 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
331 GstObject * parent, GstEvent * event);
332 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
333 GstObject * parent, GstBuffer * buffer);
334 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
335 GstObject * parent, GstPad * peer);
336 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
338 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
339 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
342 static void gst_base_text_overlay_finalize (GObject * object);
343 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
344 const GValue * value, GParamSpec * pspec);
345 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
346 GValue * value, GParamSpec * pspec);
348 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
349 PangoFontDescription * desc);
352 gst_base_text_overlay_get_type (void)
354 static GType type = 0;
356 if (g_once_init_enter ((gsize *) & type)) {
357 static const GTypeInfo info = {
358 sizeof (GstBaseTextOverlayClass),
359 (GBaseInitFunc) gst_base_text_overlay_base_init,
361 (GClassInitFunc) gst_base_text_overlay_class_init,
364 sizeof (GstBaseTextOverlay),
366 (GInstanceInitFunc) gst_base_text_overlay_init,
369 g_once_init_leave ((gsize *) & type,
370 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
378 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
379 GstBuffer * video_frame)
381 return g_strdup (overlay->default_text);
385 gst_base_text_overlay_base_init (gpointer g_class)
387 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
388 PangoFontMap *fontmap;
390 /* Only lock for the subclasses here, the base class
391 * doesn't have this mutex yet and it's not necessary
393 if (klass->pango_lock)
394 g_mutex_lock (klass->pango_lock);
395 fontmap = pango_cairo_font_map_get_default ();
396 klass->pango_context =
397 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
398 if (klass->pango_lock)
399 g_mutex_unlock (klass->pango_lock);
403 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
405 GObjectClass *gobject_class;
406 GstElementClass *gstelement_class;
408 gobject_class = (GObjectClass *) klass;
409 gstelement_class = (GstElementClass *) klass;
411 parent_class = g_type_class_peek_parent (klass);
413 gobject_class->finalize = gst_base_text_overlay_finalize;
414 gobject_class->set_property = gst_base_text_overlay_set_property;
415 gobject_class->get_property = gst_base_text_overlay_get_property;
417 gst_element_class_add_pad_template (gstelement_class,
418 gst_static_pad_template_get (&src_template_factory));
419 gst_element_class_add_pad_template (gstelement_class,
420 gst_static_pad_template_get (&video_sink_template_factory));
422 gstelement_class->change_state =
423 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
425 klass->pango_lock = g_slice_new (GMutex);
426 g_mutex_init (klass->pango_lock);
428 klass->get_text = gst_base_text_overlay_get_text;
430 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
431 g_param_spec_string ("text", "text",
432 "Text to be display.", DEFAULT_PROP_TEXT,
433 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
434 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
435 g_param_spec_boolean ("shaded-background", "shaded background",
436 "Whether to shade the background under the text area",
437 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
438 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
439 g_param_spec_enum ("valignment", "vertical alignment",
440 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
441 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
442 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
443 g_param_spec_enum ("halignment", "horizontal alignment",
444 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
445 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
446 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
447 g_param_spec_int ("xpad", "horizontal paddding",
448 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
449 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
450 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
451 g_param_spec_int ("ypad", "vertical padding",
452 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
453 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
454 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
455 g_param_spec_int ("deltax", "X position modifier",
456 "Shift X position to the left or to the right. Unit is pixels.",
457 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
458 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
459 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
460 g_param_spec_int ("deltay", "Y position modifier",
461 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
462 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
464 * GstBaseTextOverlay:xpos
466 * Horizontal position of the rendered text when using positioned alignment.
470 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
471 g_param_spec_double ("xpos", "horizontal position",
472 "Horizontal position when using position alignment", 0, 1.0,
474 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
476 * GstBaseTextOverlay:ypos
478 * Vertical position of the rendered text when using positioned alignment.
482 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
483 g_param_spec_double ("ypos", "vertical position",
484 "Vertical position when using position alignment", 0, 1.0,
486 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
487 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
488 g_param_spec_enum ("wrap-mode", "wrap mode",
489 "Whether to wrap the text and if so how.",
490 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
491 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
492 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
493 g_param_spec_string ("font-desc", "font description",
494 "Pango font description of font to be used for rendering. "
495 "See documentation of pango_font_description_from_string "
496 "for syntax.", DEFAULT_PROP_FONT_DESC,
497 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
499 * GstBaseTextOverlay:color
501 * Color of the rendered text.
505 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
506 g_param_spec_uint ("color", "Color",
507 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
509 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
511 * GstTextOverlay:outline-color
513 * Color of the outline of the rendered text.
517 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
518 g_param_spec_uint ("outline-color", "Text Outline Color",
519 "Color to use for outline the text (big-endian ARGB).", 0,
520 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
521 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
524 * GstBaseTextOverlay:line-alignment
526 * Alignment of text lines relative to each other (for multi-line text)
530 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
531 g_param_spec_enum ("line-alignment", "line alignment",
532 "Alignment of text lines relative to each other.",
533 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
534 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
536 * GstBaseTextOverlay:silent
538 * If set, no text is rendered. Useful to switch off text rendering
539 * temporarily without removing the textoverlay element from the pipeline.
543 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
544 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
545 g_param_spec_boolean ("silent", "silent",
546 "Whether to render the text string",
548 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
550 * GstBaseTextOverlay:wait-text
552 * If set, the video will block until a subtitle is received on the text pad.
553 * If video and subtitles are sent in sync, like from the same demuxer, this
554 * property should be set.
558 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
559 g_param_spec_boolean ("wait-text", "Wait Text",
560 "Whether to wait for subtitles",
561 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
563 g_object_class_install_property (G_OBJECT_CLASS (klass),
564 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
565 "Automatically adjust font size to screen-size.",
566 DEFAULT_PROP_AUTO_ADJUST_SIZE,
567 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
569 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
570 g_param_spec_boolean ("vertical-render", "vertical render",
571 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
572 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
576 gst_base_text_overlay_finalize (GObject * object)
578 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
580 g_free (overlay->default_text);
582 if (overlay->composition) {
583 gst_video_overlay_composition_unref (overlay->composition);
584 overlay->composition = NULL;
587 if (overlay->text_image) {
588 gst_buffer_unref (overlay->text_image);
589 overlay->text_image = NULL;
592 if (overlay->layout) {
593 g_object_unref (overlay->layout);
594 overlay->layout = NULL;
597 if (overlay->text_buffer) {
598 gst_buffer_unref (overlay->text_buffer);
599 overlay->text_buffer = NULL;
602 g_mutex_clear (&overlay->lock);
603 g_cond_clear (&overlay->cond);
605 G_OBJECT_CLASS (parent_class)->finalize (object);
609 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
610 GstBaseTextOverlayClass * klass)
612 GstPadTemplate *template;
613 PangoFontDescription *desc;
616 template = gst_static_pad_template_get (&video_sink_template_factory);
617 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
618 gst_object_unref (template);
619 gst_pad_set_event_function (overlay->video_sinkpad,
620 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
621 gst_pad_set_chain_function (overlay->video_sinkpad,
622 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
623 gst_pad_set_query_function (overlay->video_sinkpad,
624 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
625 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
626 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
629 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
633 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
635 gst_pad_set_event_function (overlay->text_sinkpad,
636 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
637 gst_pad_set_chain_function (overlay->text_sinkpad,
638 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
639 gst_pad_set_link_function (overlay->text_sinkpad,
640 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
641 gst_pad_set_unlink_function (overlay->text_sinkpad,
642 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
643 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
647 template = gst_static_pad_template_get (&src_template_factory);
648 overlay->srcpad = gst_pad_new_from_template (template, "src");
649 gst_object_unref (template);
650 gst_pad_set_event_function (overlay->srcpad,
651 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
652 gst_pad_set_query_function (overlay->srcpad,
653 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
654 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
656 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
657 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
659 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
660 (overlay)->pango_context);
662 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
663 (overlay)->pango_context);
664 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
666 overlay->color = DEFAULT_PROP_COLOR;
667 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
668 overlay->halign = DEFAULT_PROP_HALIGNMENT;
669 overlay->valign = DEFAULT_PROP_VALIGNMENT;
670 overlay->xpad = DEFAULT_PROP_XPAD;
671 overlay->ypad = DEFAULT_PROP_YPAD;
672 overlay->deltax = DEFAULT_PROP_DELTAX;
673 overlay->deltay = DEFAULT_PROP_DELTAY;
674 overlay->xpos = DEFAULT_PROP_XPOS;
675 overlay->ypos = DEFAULT_PROP_YPOS;
677 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
679 overlay->want_shading = DEFAULT_PROP_SHADING;
680 overlay->shading_value = DEFAULT_SHADING_VALUE;
681 overlay->silent = DEFAULT_PROP_SILENT;
682 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
683 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
685 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
686 overlay->need_render = TRUE;
687 overlay->text_image = NULL;
688 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
689 gst_base_text_overlay_update_render_mode (overlay);
691 overlay->text_buffer = NULL;
692 overlay->text_linked = FALSE;
693 g_mutex_init (&overlay->lock);
694 g_cond_init (&overlay->cond);
695 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
696 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
700 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
702 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
703 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
704 pango_layout_set_width (overlay->layout, -1);
708 if (overlay->auto_adjust_size) {
709 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
710 if (overlay->use_vertical_render) {
711 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
715 (overlay->use_vertical_render ? overlay->height : overlay->width) *
719 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
720 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
721 pango_layout_set_width (overlay->layout, width);
722 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
727 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
729 PangoMatrix matrix = PANGO_MATRIX_INIT;
730 PangoContext *context = pango_layout_get_context (overlay->layout);
732 if (overlay->use_vertical_render) {
733 pango_matrix_rotate (&matrix, -90);
734 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
735 pango_context_set_matrix (context, &matrix);
736 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
738 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
739 pango_context_set_matrix (context, &matrix);
740 pango_layout_set_alignment (overlay->layout,
741 (PangoAlignment) overlay->line_align);
746 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
748 GstStructure *structure;
751 structure = gst_caps_get_structure (caps, 0);
752 format = gst_structure_get_string (structure, "format");
753 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
758 /* FIXME: upstream nego (e.g. when the video window is resized) */
760 /* only negotiate/query video overlay composition support for now */
762 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
766 gboolean attach = FALSE;
768 GST_DEBUG_OBJECT (overlay, "performing negotiation");
770 target = gst_pad_get_current_caps (overlay->srcpad);
772 if (!target || gst_caps_is_empty (target))
775 /* find supported meta */
776 query = gst_query_new_allocation (target, TRUE);
778 if (!gst_pad_peer_query (overlay->srcpad, query)) {
779 /* no problem, we use the query defaults */
780 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
783 if (gst_query_find_allocation_meta (query,
784 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
787 overlay->attach_compo_to_buffer = attach;
789 gst_query_unref (query);
790 gst_caps_unref (target);
797 gst_caps_unref (target);
803 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
806 gboolean ret = FALSE;
808 if (!gst_video_info_from_caps (&info, caps))
811 overlay->info = info;
812 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
813 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
814 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
816 ret = gst_pad_set_caps (overlay->srcpad, caps);
819 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
820 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
821 gst_base_text_overlay_negotiate (overlay);
822 gst_base_text_overlay_update_wrap_mode (overlay);
823 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
824 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
832 GST_DEBUG_OBJECT (overlay, "could not parse caps");
838 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
839 const GValue * value, GParamSpec * pspec)
841 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
843 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
846 g_free (overlay->default_text);
847 overlay->default_text = g_value_dup_string (value);
848 overlay->need_render = TRUE;
851 overlay->want_shading = g_value_get_boolean (value);
854 overlay->xpad = g_value_get_int (value);
857 overlay->ypad = g_value_get_int (value);
860 overlay->deltax = g_value_get_int (value);
863 overlay->deltay = g_value_get_int (value);
866 overlay->xpos = g_value_get_double (value);
869 overlay->ypos = g_value_get_double (value);
871 case PROP_VALIGNMENT:
872 overlay->valign = g_value_get_enum (value);
874 case PROP_HALIGNMENT:
875 overlay->halign = g_value_get_enum (value);
878 overlay->wrap_mode = g_value_get_enum (value);
879 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
880 gst_base_text_overlay_update_wrap_mode (overlay);
881 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
885 PangoFontDescription *desc;
886 const gchar *fontdesc_str;
888 fontdesc_str = g_value_get_string (value);
889 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
890 desc = pango_font_description_from_string (fontdesc_str);
892 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
893 pango_layout_set_font_description (overlay->layout, desc);
894 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
895 pango_font_description_free (desc);
897 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
900 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
904 overlay->color = g_value_get_uint (value);
906 case PROP_OUTLINE_COLOR:
907 overlay->outline_color = g_value_get_uint (value);
910 overlay->silent = g_value_get_boolean (value);
912 case PROP_LINE_ALIGNMENT:
913 overlay->line_align = g_value_get_enum (value);
914 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
915 pango_layout_set_alignment (overlay->layout,
916 (PangoAlignment) overlay->line_align);
917 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
920 overlay->wait_text = g_value_get_boolean (value);
922 case PROP_AUTO_ADJUST_SIZE:
923 overlay->auto_adjust_size = g_value_get_boolean (value);
924 overlay->need_render = TRUE;
926 case PROP_VERTICAL_RENDER:
927 overlay->use_vertical_render = g_value_get_boolean (value);
928 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
929 gst_base_text_overlay_update_render_mode (overlay);
930 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
931 overlay->need_render = TRUE;
934 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
938 overlay->need_render = TRUE;
939 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
943 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
944 GValue * value, GParamSpec * pspec)
946 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
948 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
951 g_value_set_string (value, overlay->default_text);
954 g_value_set_boolean (value, overlay->want_shading);
957 g_value_set_int (value, overlay->xpad);
960 g_value_set_int (value, overlay->ypad);
963 g_value_set_int (value, overlay->deltax);
966 g_value_set_int (value, overlay->deltay);
969 g_value_set_double (value, overlay->xpos);
972 g_value_set_double (value, overlay->ypos);
974 case PROP_VALIGNMENT:
975 g_value_set_enum (value, overlay->valign);
977 case PROP_HALIGNMENT:
978 g_value_set_enum (value, overlay->halign);
981 g_value_set_enum (value, overlay->wrap_mode);
984 g_value_set_boolean (value, overlay->silent);
986 case PROP_LINE_ALIGNMENT:
987 g_value_set_enum (value, overlay->line_align);
990 g_value_set_boolean (value, overlay->wait_text);
992 case PROP_AUTO_ADJUST_SIZE:
993 g_value_set_boolean (value, overlay->auto_adjust_size);
995 case PROP_VERTICAL_RENDER:
996 g_value_set_boolean (value, overlay->use_vertical_render);
999 g_value_set_uint (value, overlay->color);
1001 case PROP_OUTLINE_COLOR:
1002 g_value_set_uint (value, overlay->outline_color);
1005 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1009 overlay->need_render = TRUE;
1010 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1014 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1017 gboolean ret = FALSE;
1018 GstBaseTextOverlay *overlay;
1020 overlay = GST_BASE_TEXT_OVERLAY (parent);
1022 switch (GST_QUERY_TYPE (query)) {
1023 case GST_QUERY_CAPS:
1025 GstCaps *filter, *caps;
1027 gst_query_parse_caps (query, &filter);
1028 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1029 gst_query_set_caps_result (query, caps);
1030 gst_caps_unref (caps);
1035 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1043 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1046 gboolean ret = FALSE;
1047 GstBaseTextOverlay *overlay = NULL;
1049 overlay = GST_BASE_TEXT_OVERLAY (parent);
1051 switch (GST_EVENT_TYPE (event)) {
1052 case GST_EVENT_SEEK:{
1055 /* We don't handle seek if we have not text pad */
1056 if (!overlay->text_linked) {
1057 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1058 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1062 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1064 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1066 /* Flush downstream, only for flushing seek */
1067 if (flags & GST_SEEK_FLAG_FLUSH)
1068 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1070 /* Mark ourself as flushing, unblock chains */
1071 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1072 overlay->video_flushing = TRUE;
1073 overlay->text_flushing = TRUE;
1074 gst_base_text_overlay_pop_text (overlay);
1075 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1077 /* Seek on each sink pad */
1078 gst_event_ref (event);
1079 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1081 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1083 gst_event_unref (event);
1088 if (overlay->text_linked) {
1089 gst_event_ref (event);
1090 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1091 gst_pad_push_event (overlay->text_sinkpad, event);
1093 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1104 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1110 if (G_UNLIKELY (!overlay))
1111 return gst_pad_get_pad_template_caps (pad);
1113 if (pad == overlay->srcpad)
1114 otherpad = overlay->video_sinkpad;
1116 otherpad = overlay->srcpad;
1118 /* we can do what the peer can */
1119 caps = gst_pad_peer_query_caps (otherpad, filter);
1121 GstCaps *temp, *templ;
1123 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1125 /* filtered against our padtemplate */
1126 templ = gst_pad_get_pad_template_caps (otherpad);
1127 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1128 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1129 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1130 gst_caps_unref (caps);
1131 gst_caps_unref (templ);
1132 /* this is what we can do */
1135 /* no peer, our padtemplate is enough then */
1136 caps = gst_pad_get_pad_template_caps (pad);
1138 GstCaps *intersection;
1141 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1142 gst_caps_unref (caps);
1143 caps = intersection;
1147 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1153 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1154 PangoFontDescription * desc)
1156 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1157 overlay->shadow_offset = (double) (font_size) / 13.0;
1158 overlay->outline_offset = (double) (font_size) / 15.0;
1159 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1160 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1164 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1165 gint * xpos, gint * ypos)
1168 GstBaseTextOverlayVAlign valign;
1169 GstBaseTextOverlayHAlign halign;
1171 width = overlay->image_width;
1172 height = overlay->image_height;
1174 if (overlay->use_vertical_render)
1175 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1177 halign = overlay->halign;
1180 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1181 *xpos = overlay->xpad;
1183 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1184 *xpos = (overlay->width - width) / 2;
1186 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1187 *xpos = overlay->width - width - overlay->xpad;
1189 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1190 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1191 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1198 *xpos += overlay->deltax;
1200 if (overlay->use_vertical_render)
1201 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1203 valign = overlay->valign;
1206 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1207 *ypos = overlay->height - height - overlay->ypad;
1209 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1210 *ypos = overlay->height - (height + overlay->ypad);
1212 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1213 *ypos = overlay->ypad;
1215 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1216 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1217 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1219 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1220 *ypos = (overlay->height - height) / 2;
1223 *ypos = overlay->ypad;
1226 *ypos += overlay->deltay;
1230 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1233 GstVideoOverlayRectangle *rectangle;
1235 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1237 if (overlay->text_image) {
1238 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1239 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1240 overlay->image_width, overlay->image_height);
1241 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1242 xpos, ypos, overlay->image_width, overlay->image_height,
1243 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1245 if (overlay->composition)
1246 gst_video_overlay_composition_unref (overlay->composition);
1247 overlay->composition = gst_video_overlay_composition_new (rectangle);
1248 gst_video_overlay_rectangle_unref (rectangle);
1250 } else if (overlay->composition) {
1251 gst_video_overlay_composition_unref (overlay->composition);
1252 overlay->composition = NULL;
1257 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1259 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1267 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1268 const gchar * string, gint textlen)
1271 cairo_surface_t *surface;
1272 PangoRectangle ink_rect, logical_rect;
1273 cairo_matrix_t cairo_matrix;
1275 double scalef = 1.0;
1280 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1282 if (overlay->auto_adjust_size) {
1283 /* 640 pixel is default */
1284 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1286 pango_layout_set_width (overlay->layout, -1);
1287 /* set text on pango layout */
1288 pango_layout_set_markup (overlay->layout, string, textlen);
1290 /* get subtitle image size */
1291 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1293 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1295 if (width + overlay->deltax >
1296 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1298 * subtitle image width is larger then overlay width
1299 * so rearrange overlay wrap mode.
1301 gst_base_text_overlay_update_wrap_mode (overlay);
1302 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1303 width = overlay->width;
1307 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1308 if (height > overlay->height) {
1309 height = overlay->height;
1311 if (overlay->use_vertical_render) {
1312 PangoRectangle rect;
1313 PangoContext *context;
1314 PangoMatrix matrix = PANGO_MATRIX_INIT;
1317 context = pango_layout_get_context (overlay->layout);
1319 pango_matrix_rotate (&matrix, -90);
1321 rect.x = rect.y = 0;
1323 rect.height = height;
1324 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1325 matrix.x0 = -rect.x;
1326 matrix.y0 = -rect.y;
1328 pango_context_set_matrix (context, &matrix);
1330 cairo_matrix.xx = matrix.xx;
1331 cairo_matrix.yx = matrix.yx;
1332 cairo_matrix.xy = matrix.xy;
1333 cairo_matrix.yy = matrix.yy;
1334 cairo_matrix.x0 = matrix.x0;
1335 cairo_matrix.y0 = matrix.y0;
1336 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1342 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1345 /* reallocate overlay buffer */
1346 buffer = gst_buffer_new_and_alloc (4 * width * height);
1347 gst_buffer_replace (&overlay->text_image, buffer);
1348 gst_buffer_unref (buffer);
1350 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1351 surface = cairo_image_surface_create_for_data (map.data,
1352 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1353 cr = cairo_create (surface);
1356 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1359 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1361 if (overlay->want_shading)
1362 cairo_paint_with_alpha (cr, overlay->shading_value);
1364 /* apply transformations */
1365 cairo_set_matrix (cr, &cairo_matrix);
1367 /* FIXME: We use show_layout everywhere except for the surface
1368 * because it's really faster and internally does all kinds of
1369 * caching. Unfortunately we have to paint to a cairo path for
1370 * the outline and this is slow. Once Pango supports user fonts
1371 * we should use them, see
1372 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1374 * Idea would the be, to create a cairo user font that
1375 * does shadow, outline, text painting in the
1376 * render_glyph function.
1379 /* draw shadow text */
1381 PangoAttrList *origin_attr, *filtered_attr;
1384 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1386 pango_attr_list_filter (pango_attr_list_copy (origin_attr),
1387 gst_text_overlay_filter_foreground_attr, NULL);
1390 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1391 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1392 pango_layout_set_attributes (overlay->layout, filtered_attr);
1393 pango_cairo_show_layout (cr, overlay->layout);
1394 pango_layout_set_attributes (overlay->layout, origin_attr);
1395 pango_attr_list_unref (filtered_attr);
1396 pango_attr_list_unref (origin_attr);
1400 a = (overlay->outline_color >> 24) & 0xff;
1401 r = (overlay->outline_color >> 16) & 0xff;
1402 g = (overlay->outline_color >> 8) & 0xff;
1403 b = (overlay->outline_color >> 0) & 0xff;
1405 /* draw outline text */
1407 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1408 cairo_set_line_width (cr, overlay->outline_offset);
1409 pango_cairo_layout_path (cr, overlay->layout);
1413 a = (overlay->color >> 24) & 0xff;
1414 r = (overlay->color >> 16) & 0xff;
1415 g = (overlay->color >> 8) & 0xff;
1416 b = (overlay->color >> 0) & 0xff;
1420 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1421 pango_cairo_show_layout (cr, overlay->layout);
1425 cairo_surface_destroy (surface);
1426 gst_buffer_unmap (buffer, &map);
1427 overlay->image_width = width;
1428 overlay->image_height = height;
1429 overlay->baseline_y = ink_rect.y;
1430 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1432 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 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1449 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1451 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1452 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1454 for (i = y0; i < y1; ++i) {
1455 for (j = x0; j < x1; ++j) {
1456 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1458 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1464 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1465 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1468 guint dest_stride, pixel_stride;
1471 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1472 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1473 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1475 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1476 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1478 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1479 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1482 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1484 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1487 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1489 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1491 for (i = y0; i < y1; i++) {
1492 for (j = x0; j < x1; j++) {
1496 y_pos = (i * dest_stride) + j * pixel_stride;
1497 y = dest_ptr[y_pos] + overlay->shading_value;
1499 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1504 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1505 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1506 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1508 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1509 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1514 dest_ptr = dest->data[0];
1516 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1517 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1519 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1520 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1522 for (i = y0; i < y1; i++) {
1523 for (j = x0; j < x1; j++) {
1526 y_pos = (i * 4 * overlay->width) + j * 4;
1527 for (k = 0; k < 4; k++) {
1528 y = dest_ptr[y_pos + k] + overlay->shading_value;
1529 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1535 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1536 static inline void \
1537 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1538 gint x0, gint x1, gint y0, gint y1) \
1543 dest_ptr = dest->data[0];\
1545 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1546 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1548 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1549 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1551 for (i = y0; i < y1; i++) {\
1552 for (j = x0; j < x1; j++) {\
1554 y_pos = (i * 4 * overlay->width) + j * 4;\
1555 for (k = OFFSET; k < 3+OFFSET; k++) {\
1556 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1557 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1562 ARGB_SHADE_FUNCTION (ARGB, 1);
1563 ARGB_SHADE_FUNCTION (ABGR, 1);
1564 ARGB_SHADE_FUNCTION (RGBA, 0);
1565 ARGB_SHADE_FUNCTION (BGRA, 0);
1568 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1569 const gchar * text, gint textlen)
1573 if (!overlay->need_render) {
1574 GST_DEBUG ("Using previously rendered text.");
1578 /* -1 is the whole string */
1579 if (text != NULL && textlen < 0) {
1580 textlen = strlen (text);
1584 string = g_strndup (text, textlen);
1585 } else { /* empty string */
1586 string = g_strdup (" ");
1588 g_strdelimit (string, "\r\t", ' ');
1589 textlen = strlen (string);
1591 /* FIXME: should we check for UTF-8 here? */
1593 GST_DEBUG ("Rendering '%s'", string);
1594 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1598 overlay->need_render = FALSE;
1601 static GstFlowReturn
1602 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1603 GstBuffer * video_frame)
1606 GstVideoFrame frame;
1608 if (overlay->composition == NULL)
1611 if (gst_pad_check_reconfigure (overlay->srcpad))
1612 gst_base_text_overlay_negotiate (overlay);
1614 video_frame = gst_buffer_make_writable (video_frame);
1616 if (overlay->attach_compo_to_buffer) {
1617 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1618 gst_buffer_add_video_overlay_composition_meta (video_frame,
1619 overlay->composition);
1620 /* FIXME: emulate shaded background box if want_shading=true */
1624 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1628 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1630 /* shaded background box */
1631 if (overlay->want_shading) {
1632 switch (overlay->format) {
1633 case GST_VIDEO_FORMAT_I420:
1634 case GST_VIDEO_FORMAT_NV12:
1635 case GST_VIDEO_FORMAT_NV21:
1636 gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1637 xpos, xpos + overlay->image_width,
1638 ypos, ypos + overlay->image_height);
1640 case GST_VIDEO_FORMAT_AYUV:
1641 case GST_VIDEO_FORMAT_UYVY:
1642 gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1643 xpos, xpos + overlay->image_width,
1644 ypos, ypos + overlay->image_height);
1646 case GST_VIDEO_FORMAT_xRGB:
1647 gst_base_text_overlay_shade_xRGB (overlay, &frame,
1648 xpos, xpos + overlay->image_width,
1649 ypos, ypos + overlay->image_height);
1651 case GST_VIDEO_FORMAT_xBGR:
1652 gst_base_text_overlay_shade_xBGR (overlay, &frame,
1653 xpos, xpos + overlay->image_width,
1654 ypos, ypos + overlay->image_height);
1656 case GST_VIDEO_FORMAT_BGRx:
1657 gst_base_text_overlay_shade_BGRx (overlay, &frame,
1658 xpos, xpos + overlay->image_width,
1659 ypos, ypos + overlay->image_height);
1661 case GST_VIDEO_FORMAT_RGBx:
1662 gst_base_text_overlay_shade_RGBx (overlay, &frame,
1663 xpos, xpos + overlay->image_width,
1664 ypos, ypos + overlay->image_height);
1666 case GST_VIDEO_FORMAT_ARGB:
1667 gst_base_text_overlay_shade_ARGB (overlay, &frame,
1668 xpos, xpos + overlay->image_width,
1669 ypos, ypos + overlay->image_height);
1671 case GST_VIDEO_FORMAT_ABGR:
1672 gst_base_text_overlay_shade_ABGR (overlay, &frame,
1673 xpos, xpos + overlay->image_width,
1674 ypos, ypos + overlay->image_height);
1676 case GST_VIDEO_FORMAT_RGBA:
1677 gst_base_text_overlay_shade_RGBA (overlay, &frame,
1678 xpos, xpos + overlay->image_width,
1679 ypos, ypos + overlay->image_height);
1681 case GST_VIDEO_FORMAT_BGRA:
1682 gst_base_text_overlay_shade_BGRA (overlay, &frame,
1683 xpos, xpos + overlay->image_width,
1684 ypos, ypos + overlay->image_height);
1687 GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
1688 gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (&frame)));
1693 gst_video_overlay_composition_blend (overlay->composition, &frame);
1695 gst_video_frame_unmap (&frame);
1699 return gst_pad_push (overlay->srcpad, video_frame);
1704 gst_buffer_unref (video_frame);
1705 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1710 static GstPadLinkReturn
1711 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
1714 GstBaseTextOverlay *overlay;
1716 overlay = GST_BASE_TEXT_OVERLAY (parent);
1717 if (G_UNLIKELY (!overlay))
1718 return GST_PAD_LINK_REFUSED;
1720 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1722 overlay->text_linked = TRUE;
1724 return GST_PAD_LINK_OK;
1728 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
1730 GstBaseTextOverlay *overlay;
1732 /* don't use gst_pad_get_parent() here, will deadlock */
1733 overlay = GST_BASE_TEXT_OVERLAY (parent);
1735 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1737 overlay->text_linked = FALSE;
1739 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1743 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1746 gboolean ret = FALSE;
1747 GstBaseTextOverlay *overlay = NULL;
1749 overlay = GST_BASE_TEXT_OVERLAY (parent);
1751 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1753 switch (GST_EVENT_TYPE (event)) {
1754 case GST_EVENT_CAPS:
1758 gst_event_parse_caps (event, &caps);
1759 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1760 gst_event_unref (event);
1763 case GST_EVENT_SEGMENT:
1765 const GstSegment *segment;
1767 overlay->text_eos = FALSE;
1769 gst_event_parse_segment (event, &segment);
1771 if (segment->format == GST_FORMAT_TIME) {
1772 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1773 gst_segment_copy_into (segment, &overlay->text_segment);
1774 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1775 &overlay->text_segment);
1776 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1778 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1779 ("received non-TIME newsegment event on text input"));
1782 gst_event_unref (event);
1785 /* wake up the video chain, it might be waiting for a text buffer or
1786 * a text segment update */
1787 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1788 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1789 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1794 GstClockTime start, duration;
1796 gst_event_parse_gap (event, &start, &duration);
1797 if (GST_CLOCK_TIME_IS_VALID (duration))
1799 /* we do not expect another buffer until after gap,
1800 * so that is our position now */
1801 overlay->text_segment.position = start;
1803 /* wake up the video chain, it might be waiting for a text buffer or
1804 * a text segment update */
1805 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1806 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1807 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1810 case GST_EVENT_FLUSH_STOP:
1811 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1812 GST_INFO_OBJECT (overlay, "text flush stop");
1813 overlay->text_flushing = FALSE;
1814 overlay->text_eos = FALSE;
1815 gst_base_text_overlay_pop_text (overlay);
1816 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1817 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1818 gst_event_unref (event);
1821 case GST_EVENT_FLUSH_START:
1822 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1823 GST_INFO_OBJECT (overlay, "text flush start");
1824 overlay->text_flushing = TRUE;
1825 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1826 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1827 gst_event_unref (event);
1831 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1832 overlay->text_eos = TRUE;
1833 GST_INFO_OBJECT (overlay, "text EOS");
1834 /* wake up the video chain, it might be waiting for a text buffer or
1835 * a text segment update */
1836 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1837 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1838 gst_event_unref (event);
1842 ret = gst_pad_event_default (pad, parent, event);
1850 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1853 gboolean ret = FALSE;
1854 GstBaseTextOverlay *overlay = NULL;
1856 overlay = GST_BASE_TEXT_OVERLAY (parent);
1858 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1860 switch (GST_EVENT_TYPE (event)) {
1861 case GST_EVENT_CAPS:
1865 gst_event_parse_caps (event, &caps);
1866 ret = gst_base_text_overlay_setcaps (overlay, caps);
1867 gst_event_unref (event);
1870 case GST_EVENT_SEGMENT:
1872 const GstSegment *segment;
1874 GST_DEBUG_OBJECT (overlay, "received new segment");
1876 gst_event_parse_segment (event, &segment);
1878 if (segment->format == GST_FORMAT_TIME) {
1879 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1882 gst_segment_copy_into (segment, &overlay->segment);
1884 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1885 ("received non-TIME newsegment event on video input"));
1888 ret = gst_pad_event_default (pad, parent, event);
1892 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1893 GST_INFO_OBJECT (overlay, "video EOS");
1894 overlay->video_eos = TRUE;
1895 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1896 ret = gst_pad_event_default (pad, parent, event);
1898 case GST_EVENT_FLUSH_START:
1899 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1900 GST_INFO_OBJECT (overlay, "video flush start");
1901 overlay->video_flushing = TRUE;
1902 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1903 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1904 ret = gst_pad_event_default (pad, parent, event);
1906 case GST_EVENT_FLUSH_STOP:
1907 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1908 GST_INFO_OBJECT (overlay, "video flush stop");
1909 overlay->video_flushing = FALSE;
1910 overlay->video_eos = FALSE;
1911 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1912 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1913 ret = gst_pad_event_default (pad, parent, event);
1916 ret = gst_pad_event_default (pad, parent, event);
1924 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1927 gboolean ret = FALSE;
1928 GstBaseTextOverlay *overlay;
1930 overlay = GST_BASE_TEXT_OVERLAY (parent);
1932 switch (GST_QUERY_TYPE (query)) {
1933 case GST_QUERY_CAPS:
1935 GstCaps *filter, *caps;
1937 gst_query_parse_caps (query, &filter);
1938 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1939 gst_query_set_caps_result (query, caps);
1940 gst_caps_unref (caps);
1945 ret = gst_pad_query_default (pad, parent, query);
1952 /* Called with lock held */
1954 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1956 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1958 if (overlay->text_buffer) {
1959 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1960 overlay->text_buffer);
1961 gst_buffer_unref (overlay->text_buffer);
1962 overlay->text_buffer = NULL;
1965 /* Let the text task know we used that buffer */
1966 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1969 /* We receive text buffers here. If they are out of segment we just ignore them.
1970 If the buffer is in our segment we keep it internally except if another one
1971 is already waiting here, in that case we wait that it gets kicked out */
1972 static GstFlowReturn
1973 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1976 GstFlowReturn ret = GST_FLOW_OK;
1977 GstBaseTextOverlay *overlay = NULL;
1978 gboolean in_seg = FALSE;
1979 guint64 clip_start = 0, clip_stop = 0;
1981 overlay = GST_BASE_TEXT_OVERLAY (parent);
1983 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1985 if (overlay->text_flushing) {
1986 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1987 ret = GST_FLOW_FLUSHING;
1988 GST_LOG_OBJECT (overlay, "text flushing");
1992 if (overlay->text_eos) {
1993 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1995 GST_LOG_OBJECT (overlay, "text EOS");
1999 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2000 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2001 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2002 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2003 GST_BUFFER_DURATION (buffer)));
2005 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2008 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2009 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2011 stop = GST_CLOCK_TIME_NONE;
2013 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2014 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2020 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2021 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2022 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2023 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2025 /* Wait for the previous buffer to go away */
2026 while (overlay->text_buffer != NULL) {
2027 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2028 GST_DEBUG_PAD_NAME (pad));
2029 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2030 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2031 if (overlay->text_flushing) {
2032 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2033 ret = GST_FLOW_FLUSHING;
2038 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2039 overlay->text_segment.position = clip_start;
2041 overlay->text_buffer = buffer;
2042 /* That's a new text buffer we need to render */
2043 overlay->need_render = TRUE;
2045 /* in case the video chain is waiting for a text buffer, wake it up */
2046 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2049 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2056 static GstFlowReturn
2057 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2060 GstBaseTextOverlayClass *klass;
2061 GstBaseTextOverlay *overlay;
2062 GstFlowReturn ret = GST_FLOW_OK;
2063 gboolean in_seg = FALSE;
2064 guint64 start, stop, clip_start = 0, clip_stop = 0;
2067 overlay = GST_BASE_TEXT_OVERLAY (parent);
2068 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2070 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2071 goto missing_timestamp;
2073 /* ignore buffers that are outside of the current segment */
2074 start = GST_BUFFER_TIMESTAMP (buffer);
2076 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2077 stop = GST_CLOCK_TIME_NONE;
2079 stop = start + GST_BUFFER_DURATION (buffer);
2082 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2083 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2084 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2086 /* segment_clip() will adjust start unconditionally to segment_start if
2087 * no stop time is provided, so handle this ourselves */
2088 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2089 goto out_of_segment;
2091 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2092 &clip_start, &clip_stop);
2095 goto out_of_segment;
2097 /* if the buffer is only partially in the segment, fix up stamps */
2098 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2099 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2100 buffer = gst_buffer_make_writable (buffer);
2101 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2103 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2106 /* now, after we've done the clipping, fix up end time if there's no
2107 * duration (we only use those estimated values internally though, we
2108 * don't want to set bogus values on the buffer itself) */
2112 gint fps_num, fps_denom;
2114 /* FIXME, store this in setcaps */
2115 caps = gst_pad_get_current_caps (pad);
2116 s = gst_caps_get_structure (caps, 0);
2117 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2118 fps_num && fps_denom) {
2119 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2120 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2122 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2123 stop = start + 1; /* we need to assume some interval */
2125 gst_caps_unref (caps);
2128 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2132 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2134 if (overlay->video_flushing)
2137 if (overlay->video_eos)
2140 if (overlay->silent) {
2141 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2142 ret = gst_pad_push (overlay->srcpad, buffer);
2144 /* Update position */
2145 overlay->segment.position = clip_start;
2150 /* Text pad not linked, rendering internal text */
2151 if (!overlay->text_linked) {
2152 if (klass->get_text) {
2153 text = klass->get_text (overlay, buffer);
2155 text = g_strdup (overlay->default_text);
2158 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2159 "text: '%s'", GST_STR_NULL (text));
2161 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2163 if (text != NULL && *text != '\0') {
2164 /* Render and push */
2165 gst_base_text_overlay_render_text (overlay, text, -1);
2166 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2168 /* Invalid or empty string */
2169 ret = gst_pad_push (overlay->srcpad, buffer);
2172 /* Text pad linked, check if we have a text buffer queued */
2173 if (overlay->text_buffer) {
2174 gboolean pop_text = FALSE, valid_text_time = TRUE;
2175 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2176 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2177 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2178 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2179 GstClockTime vid_running_time, vid_running_time_end;
2181 /* if the text buffer isn't stamped right, pop it off the
2182 * queue and display it for the current video frame only */
2183 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2184 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2185 GST_WARNING_OBJECT (overlay,
2186 "Got text buffer with invalid timestamp or duration");
2188 valid_text_time = FALSE;
2190 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2191 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2195 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2197 vid_running_time_end =
2198 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2201 /* If timestamp and duration are valid */
2202 if (valid_text_time) {
2204 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2206 text_running_time_end =
2207 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2211 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2212 GST_TIME_ARGS (text_running_time),
2213 GST_TIME_ARGS (text_running_time_end));
2214 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2215 GST_TIME_ARGS (vid_running_time),
2216 GST_TIME_ARGS (vid_running_time_end));
2218 /* Text too old or in the future */
2219 if (valid_text_time && text_running_time_end <= vid_running_time) {
2220 /* text buffer too old, get rid of it and do nothing */
2221 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2223 gst_base_text_overlay_pop_text (overlay);
2224 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2225 goto wait_for_text_buf;
2226 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2227 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2228 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2229 /* Push the video frame */
2230 ret = gst_pad_push (overlay->srcpad, buffer);
2236 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2237 in_text = (gchar *) map.data;
2241 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2242 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2243 * here on purpose, this is something that needs fixing upstream */
2244 if (!g_utf8_validate (in_text, in_size, NULL)) {
2245 const gchar *end = NULL;
2247 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2248 in_text = g_strndup (in_text, in_size);
2249 while (!g_utf8_validate (in_text, in_size, &end) && end)
2250 *((gchar *) end) = '*';
2253 /* Get the string */
2254 if (overlay->have_pango_markup) {
2255 text = g_strndup (in_text, in_size);
2257 text = g_markup_escape_text (in_text, in_size);
2260 if (text != NULL && *text != '\0') {
2261 gint text_len = strlen (text);
2263 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2264 text[text_len - 1] == '\r')) {
2267 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2268 gst_base_text_overlay_render_text (overlay, text, text_len);
2270 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2271 gst_base_text_overlay_render_text (overlay, " ", 1);
2273 if (in_text != (gchar *) map.data)
2276 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2277 gst_base_text_overlay_render_text (overlay, " ", 1);
2280 gst_buffer_unmap (overlay->text_buffer, &map);
2282 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2283 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2285 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2286 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2291 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2292 gst_base_text_overlay_pop_text (overlay);
2293 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2296 gboolean wait_for_text_buf = TRUE;
2298 if (overlay->text_eos)
2299 wait_for_text_buf = FALSE;
2301 if (!overlay->wait_text)
2302 wait_for_text_buf = FALSE;
2304 /* Text pad linked, but no text buffer available - what now? */
2305 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2306 GstClockTime text_start_running_time, text_position_running_time;
2307 GstClockTime vid_running_time;
2310 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2311 GST_BUFFER_TIMESTAMP (buffer));
2312 text_start_running_time =
2313 gst_segment_to_running_time (&overlay->text_segment,
2314 GST_FORMAT_TIME, overlay->text_segment.start);
2315 text_position_running_time =
2316 gst_segment_to_running_time (&overlay->text_segment,
2317 GST_FORMAT_TIME, overlay->text_segment.position);
2319 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2320 vid_running_time < text_start_running_time) ||
2321 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2322 vid_running_time < text_position_running_time)) {
2323 wait_for_text_buf = FALSE;
2327 if (wait_for_text_buf) {
2328 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2329 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2330 GST_DEBUG_OBJECT (overlay, "resuming");
2331 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2332 goto wait_for_text_buf;
2334 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2335 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2336 ret = gst_pad_push (overlay->srcpad, buffer);
2343 /* Update position */
2344 overlay->segment.position = clip_start;
2350 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2351 gst_buffer_unref (buffer);
2357 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2358 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2359 gst_buffer_unref (buffer);
2360 return GST_FLOW_FLUSHING;
2364 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2365 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2366 gst_buffer_unref (buffer);
2367 return GST_FLOW_EOS;
2371 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2372 gst_buffer_unref (buffer);
2377 static GstStateChangeReturn
2378 gst_base_text_overlay_change_state (GstElement * element,
2379 GstStateChange transition)
2381 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2382 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2384 switch (transition) {
2385 case GST_STATE_CHANGE_PAUSED_TO_READY:
2386 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2387 overlay->text_flushing = TRUE;
2388 overlay->video_flushing = TRUE;
2389 /* pop_text will broadcast on the GCond and thus also make the video
2390 * chain exit if it's waiting for a text buffer */
2391 gst_base_text_overlay_pop_text (overlay);
2392 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2398 ret = parent_class->change_state (element, transition);
2399 if (ret == GST_STATE_CHANGE_FAILURE)
2402 switch (transition) {
2403 case GST_STATE_CHANGE_READY_TO_PAUSED:
2404 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2405 overlay->text_flushing = FALSE;
2406 overlay->video_flushing = FALSE;
2407 overlay->video_eos = FALSE;
2408 overlay->text_eos = FALSE;
2409 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2410 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2411 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2421 plugin_init (GstPlugin * plugin)
2423 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2424 GST_TYPE_TEXT_OVERLAY) ||
2425 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2426 GST_TYPE_TIME_OVERLAY) ||
2427 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2428 GST_TYPE_CLOCK_OVERLAY) ||
2429 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2430 GST_TYPE_TEXT_RENDER)) {
2434 /*texttestsrc_plugin_init(module, plugin); */
2436 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2441 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2442 pango, "Pango-based text rendering and overlay", plugin_init,
2443 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)