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., 59 Temple Place - Suite 330,
22 * Boston, MA 02111-1307, 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,
336 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad);
337 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
338 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
341 static void gst_base_text_overlay_finalize (GObject * object);
342 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
343 const GValue * value, GParamSpec * pspec);
344 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
345 GValue * value, GParamSpec * pspec);
347 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
348 PangoFontDescription * desc);
351 gst_base_text_overlay_get_type (void)
353 static GType type = 0;
355 if (g_once_init_enter ((gsize *) & type)) {
356 static const GTypeInfo info = {
357 sizeof (GstBaseTextOverlayClass),
358 (GBaseInitFunc) gst_base_text_overlay_base_init,
360 (GClassInitFunc) gst_base_text_overlay_class_init,
363 sizeof (GstBaseTextOverlay),
365 (GInstanceInitFunc) gst_base_text_overlay_init,
368 g_once_init_leave ((gsize *) & type,
369 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
377 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
378 GstBuffer * video_frame)
380 return g_strdup (overlay->default_text);
384 gst_base_text_overlay_base_init (gpointer g_class)
386 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
387 PangoFontMap *fontmap;
389 /* Only lock for the subclasses here, the base class
390 * doesn't have this mutex yet and it's not necessary
392 if (klass->pango_lock)
393 g_mutex_lock (klass->pango_lock);
394 fontmap = pango_cairo_font_map_get_default ();
395 klass->pango_context =
396 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
397 if (klass->pango_lock)
398 g_mutex_unlock (klass->pango_lock);
402 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
404 GObjectClass *gobject_class;
405 GstElementClass *gstelement_class;
407 gobject_class = (GObjectClass *) klass;
408 gstelement_class = (GstElementClass *) klass;
410 parent_class = g_type_class_peek_parent (klass);
412 gobject_class->finalize = gst_base_text_overlay_finalize;
413 gobject_class->set_property = gst_base_text_overlay_set_property;
414 gobject_class->get_property = gst_base_text_overlay_get_property;
416 gst_element_class_add_pad_template (gstelement_class,
417 gst_static_pad_template_get (&src_template_factory));
418 gst_element_class_add_pad_template (gstelement_class,
419 gst_static_pad_template_get (&video_sink_template_factory));
421 gstelement_class->change_state =
422 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
424 klass->pango_lock = g_slice_new (GMutex);
425 g_mutex_init (klass->pango_lock);
427 klass->get_text = gst_base_text_overlay_get_text;
429 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
430 g_param_spec_string ("text", "text",
431 "Text to be display.", DEFAULT_PROP_TEXT,
432 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
433 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
434 g_param_spec_boolean ("shaded-background", "shaded background",
435 "Whether to shade the background under the text area",
436 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
437 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
438 g_param_spec_enum ("valignment", "vertical alignment",
439 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
440 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
441 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
442 g_param_spec_enum ("halignment", "horizontal alignment",
443 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
444 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
445 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
446 g_param_spec_int ("xpad", "horizontal paddding",
447 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
448 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
449 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
450 g_param_spec_int ("ypad", "vertical padding",
451 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
452 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
453 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
454 g_param_spec_int ("deltax", "X position modifier",
455 "Shift X position to the left or to the right. Unit is pixels.",
456 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
457 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
458 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
459 g_param_spec_int ("deltay", "Y position modifier",
460 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
461 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
463 * GstBaseTextOverlay:xpos
465 * Horizontal position of the rendered text when using positioned alignment.
469 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
470 g_param_spec_double ("xpos", "horizontal position",
471 "Horizontal position when using position alignment", 0, 1.0,
473 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
475 * GstBaseTextOverlay:ypos
477 * Vertical position of the rendered text when using positioned alignment.
481 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
482 g_param_spec_double ("ypos", "vertical position",
483 "Vertical position when using position alignment", 0, 1.0,
485 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
486 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
487 g_param_spec_enum ("wrap-mode", "wrap mode",
488 "Whether to wrap the text and if so how.",
489 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
490 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
491 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
492 g_param_spec_string ("font-desc", "font description",
493 "Pango font description of font to be used for rendering. "
494 "See documentation of pango_font_description_from_string "
495 "for syntax.", DEFAULT_PROP_FONT_DESC,
496 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
498 * GstBaseTextOverlay:color
500 * Color of the rendered text.
504 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
505 g_param_spec_uint ("color", "Color",
506 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
508 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
510 * GstTextOverlay:outline-color
512 * Color of the outline of the rendered text.
516 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
517 g_param_spec_uint ("outline-color", "Text Outline Color",
518 "Color to use for outline the text (big-endian ARGB).", 0,
519 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
520 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
523 * GstBaseTextOverlay:line-alignment
525 * Alignment of text lines relative to each other (for multi-line text)
529 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
530 g_param_spec_enum ("line-alignment", "line alignment",
531 "Alignment of text lines relative to each other.",
532 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
533 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
535 * GstBaseTextOverlay:silent
537 * If set, no text is rendered. Useful to switch off text rendering
538 * temporarily without removing the textoverlay element from the pipeline.
542 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
543 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
544 g_param_spec_boolean ("silent", "silent",
545 "Whether to render the text string",
547 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
549 * GstBaseTextOverlay:wait-text
551 * If set, the video will block until a subtitle is received on the text pad.
552 * If video and subtitles are sent in sync, like from the same demuxer, this
553 * property should be set.
557 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
558 g_param_spec_boolean ("wait-text", "Wait Text",
559 "Whether to wait for subtitles",
560 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
562 g_object_class_install_property (G_OBJECT_CLASS (klass),
563 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
564 "Automatically adjust font size to screen-size.",
565 DEFAULT_PROP_AUTO_ADJUST_SIZE,
566 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
568 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
569 g_param_spec_boolean ("vertical-render", "vertical render",
570 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
571 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
575 gst_base_text_overlay_finalize (GObject * object)
577 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
579 g_free (overlay->default_text);
581 if (overlay->composition) {
582 gst_video_overlay_composition_unref (overlay->composition);
583 overlay->composition = NULL;
586 if (overlay->text_image) {
587 gst_buffer_unref (overlay->text_image);
588 overlay->text_image = NULL;
591 if (overlay->layout) {
592 g_object_unref (overlay->layout);
593 overlay->layout = NULL;
596 if (overlay->text_buffer) {
597 gst_buffer_unref (overlay->text_buffer);
598 overlay->text_buffer = NULL;
601 g_mutex_clear (&overlay->lock);
602 g_cond_clear (&overlay->cond);
604 G_OBJECT_CLASS (parent_class)->finalize (object);
608 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
609 GstBaseTextOverlayClass * klass)
611 GstPadTemplate *template;
612 PangoFontDescription *desc;
615 template = gst_static_pad_template_get (&video_sink_template_factory);
616 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
617 gst_object_unref (template);
618 gst_pad_set_event_function (overlay->video_sinkpad,
619 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
620 gst_pad_set_chain_function (overlay->video_sinkpad,
621 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
622 gst_pad_set_query_function (overlay->video_sinkpad,
623 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
624 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
627 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
631 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
633 gst_pad_set_event_function (overlay->text_sinkpad,
634 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
635 gst_pad_set_chain_function (overlay->text_sinkpad,
636 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
637 gst_pad_set_link_function (overlay->text_sinkpad,
638 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
639 gst_pad_set_unlink_function (overlay->text_sinkpad,
640 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
641 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
645 template = gst_static_pad_template_get (&src_template_factory);
646 overlay->srcpad = gst_pad_new_from_template (template, "src");
647 gst_object_unref (template);
648 gst_pad_set_event_function (overlay->srcpad,
649 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
650 gst_pad_set_query_function (overlay->srcpad,
651 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
652 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
654 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
655 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
657 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
658 (overlay)->pango_context);
660 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
661 (overlay)->pango_context);
662 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
664 overlay->color = DEFAULT_PROP_COLOR;
665 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
666 overlay->halign = DEFAULT_PROP_HALIGNMENT;
667 overlay->valign = DEFAULT_PROP_VALIGNMENT;
668 overlay->xpad = DEFAULT_PROP_XPAD;
669 overlay->ypad = DEFAULT_PROP_YPAD;
670 overlay->deltax = DEFAULT_PROP_DELTAX;
671 overlay->deltay = DEFAULT_PROP_DELTAY;
672 overlay->xpos = DEFAULT_PROP_XPOS;
673 overlay->ypos = DEFAULT_PROP_YPOS;
675 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
677 overlay->want_shading = DEFAULT_PROP_SHADING;
678 overlay->shading_value = DEFAULT_SHADING_VALUE;
679 overlay->silent = DEFAULT_PROP_SILENT;
680 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
681 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
683 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
684 overlay->need_render = TRUE;
685 overlay->text_image = NULL;
686 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
687 gst_base_text_overlay_update_render_mode (overlay);
689 overlay->text_buffer = NULL;
690 overlay->text_linked = FALSE;
691 g_mutex_init (&overlay->lock);
692 g_cond_init (&overlay->cond);
693 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
694 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
698 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
700 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
701 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
702 pango_layout_set_width (overlay->layout, -1);
706 if (overlay->auto_adjust_size) {
707 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
708 if (overlay->use_vertical_render) {
709 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
713 (overlay->use_vertical_render ? overlay->height : overlay->width) *
717 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
718 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
719 pango_layout_set_width (overlay->layout, width);
720 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
725 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
727 PangoMatrix matrix = PANGO_MATRIX_INIT;
728 PangoContext *context = pango_layout_get_context (overlay->layout);
730 if (overlay->use_vertical_render) {
731 pango_matrix_rotate (&matrix, -90);
732 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
733 pango_context_set_matrix (context, &matrix);
734 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
736 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
737 pango_context_set_matrix (context, &matrix);
738 pango_layout_set_alignment (overlay->layout,
739 (PangoAlignment) overlay->line_align);
744 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
746 GstStructure *structure;
749 structure = gst_caps_get_structure (caps, 0);
750 format = gst_structure_get_string (structure, "format");
751 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
756 /* FIXME: upstream nego (e.g. when the video window is resized) */
758 /* only negotiate/query video overlay composition support for now */
760 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
764 gboolean attach = FALSE;
766 GST_DEBUG_OBJECT (overlay, "performing negotiation");
768 target = gst_pad_get_current_caps (overlay->srcpad);
770 if (!target || gst_caps_is_empty (target))
773 /* find supported meta */
774 query = gst_query_new_allocation (target, TRUE);
776 if (!gst_pad_peer_query (overlay->srcpad, query)) {
777 /* no problem, we use the query defaults */
778 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
781 if (gst_query_find_allocation_meta (query,
782 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
785 overlay->attach_compo_to_buffer = attach;
787 gst_query_unref (query);
788 gst_caps_unref (target);
795 gst_caps_unref (target);
801 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
804 gboolean ret = FALSE;
806 if (!gst_video_info_from_caps (&info, caps))
809 overlay->info = info;
810 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
811 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
812 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
814 ret = gst_pad_set_caps (overlay->srcpad, caps);
817 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
818 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
819 gst_base_text_overlay_negotiate (overlay);
820 gst_base_text_overlay_update_wrap_mode (overlay);
821 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
822 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
830 GST_DEBUG_OBJECT (overlay, "could not parse caps");
836 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
837 const GValue * value, GParamSpec * pspec)
839 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
841 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
844 g_free (overlay->default_text);
845 overlay->default_text = g_value_dup_string (value);
846 overlay->need_render = TRUE;
849 overlay->want_shading = g_value_get_boolean (value);
852 overlay->xpad = g_value_get_int (value);
855 overlay->ypad = g_value_get_int (value);
858 overlay->deltax = g_value_get_int (value);
861 overlay->deltay = g_value_get_int (value);
864 overlay->xpos = g_value_get_double (value);
867 overlay->ypos = g_value_get_double (value);
869 case PROP_VALIGNMENT:
870 overlay->valign = g_value_get_enum (value);
872 case PROP_HALIGNMENT:
873 overlay->halign = g_value_get_enum (value);
876 overlay->wrap_mode = g_value_get_enum (value);
877 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
878 gst_base_text_overlay_update_wrap_mode (overlay);
879 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
883 PangoFontDescription *desc;
884 const gchar *fontdesc_str;
886 fontdesc_str = g_value_get_string (value);
887 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
888 desc = pango_font_description_from_string (fontdesc_str);
890 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
891 pango_layout_set_font_description (overlay->layout, desc);
892 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
893 pango_font_description_free (desc);
895 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
898 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
902 overlay->color = g_value_get_uint (value);
904 case PROP_OUTLINE_COLOR:
905 overlay->outline_color = g_value_get_uint (value);
908 overlay->silent = g_value_get_boolean (value);
910 case PROP_LINE_ALIGNMENT:
911 overlay->line_align = g_value_get_enum (value);
912 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
913 pango_layout_set_alignment (overlay->layout,
914 (PangoAlignment) overlay->line_align);
915 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
918 overlay->wait_text = g_value_get_boolean (value);
920 case PROP_AUTO_ADJUST_SIZE:
921 overlay->auto_adjust_size = g_value_get_boolean (value);
922 overlay->need_render = TRUE;
924 case PROP_VERTICAL_RENDER:
925 overlay->use_vertical_render = g_value_get_boolean (value);
926 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
927 gst_base_text_overlay_update_render_mode (overlay);
928 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
929 overlay->need_render = TRUE;
932 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
936 overlay->need_render = TRUE;
937 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
941 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
942 GValue * value, GParamSpec * pspec)
944 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
946 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
949 g_value_set_string (value, overlay->default_text);
952 g_value_set_boolean (value, overlay->want_shading);
955 g_value_set_int (value, overlay->xpad);
958 g_value_set_int (value, overlay->ypad);
961 g_value_set_int (value, overlay->deltax);
964 g_value_set_int (value, overlay->deltay);
967 g_value_set_double (value, overlay->xpos);
970 g_value_set_double (value, overlay->ypos);
972 case PROP_VALIGNMENT:
973 g_value_set_enum (value, overlay->valign);
975 case PROP_HALIGNMENT:
976 g_value_set_enum (value, overlay->halign);
979 g_value_set_enum (value, overlay->wrap_mode);
982 g_value_set_boolean (value, overlay->silent);
984 case PROP_LINE_ALIGNMENT:
985 g_value_set_enum (value, overlay->line_align);
988 g_value_set_boolean (value, overlay->wait_text);
990 case PROP_AUTO_ADJUST_SIZE:
991 g_value_set_boolean (value, overlay->auto_adjust_size);
993 case PROP_VERTICAL_RENDER:
994 g_value_set_boolean (value, overlay->use_vertical_render);
997 g_value_set_uint (value, overlay->color);
999 case PROP_OUTLINE_COLOR:
1000 g_value_set_uint (value, overlay->outline_color);
1003 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1007 overlay->need_render = TRUE;
1008 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1012 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1015 gboolean ret = FALSE;
1016 GstBaseTextOverlay *overlay;
1018 overlay = GST_BASE_TEXT_OVERLAY (parent);
1020 switch (GST_QUERY_TYPE (query)) {
1021 case GST_QUERY_CAPS:
1023 GstCaps *filter, *caps;
1025 gst_query_parse_caps (query, &filter);
1026 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1027 gst_query_set_caps_result (query, caps);
1028 gst_caps_unref (caps);
1033 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1041 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1044 gboolean ret = FALSE;
1045 GstBaseTextOverlay *overlay = NULL;
1047 overlay = GST_BASE_TEXT_OVERLAY (parent);
1049 switch (GST_EVENT_TYPE (event)) {
1050 case GST_EVENT_SEEK:{
1053 /* We don't handle seek if we have not text pad */
1054 if (!overlay->text_linked) {
1055 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1056 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1060 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1062 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1064 /* Flush downstream, only for flushing seek */
1065 if (flags & GST_SEEK_FLAG_FLUSH)
1066 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1068 /* Mark ourself as flushing, unblock chains */
1069 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1070 overlay->video_flushing = TRUE;
1071 overlay->text_flushing = TRUE;
1072 gst_base_text_overlay_pop_text (overlay);
1073 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1075 /* Seek on each sink pad */
1076 gst_event_ref (event);
1077 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1079 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1081 gst_event_unref (event);
1086 if (overlay->text_linked) {
1087 gst_event_ref (event);
1088 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1089 gst_pad_push_event (overlay->text_sinkpad, event);
1091 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1102 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1108 if (G_UNLIKELY (!overlay))
1109 return gst_pad_get_pad_template_caps (pad);
1111 if (pad == overlay->srcpad)
1112 otherpad = overlay->video_sinkpad;
1114 otherpad = overlay->srcpad;
1116 /* we can do what the peer can */
1117 caps = gst_pad_peer_query_caps (otherpad, filter);
1119 GstCaps *temp, *templ;
1121 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1123 /* filtered against our padtemplate */
1124 templ = gst_pad_get_pad_template_caps (otherpad);
1125 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1126 temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1127 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1128 gst_caps_unref (caps);
1129 gst_caps_unref (templ);
1130 /* this is what we can do */
1133 /* no peer, our padtemplate is enough then */
1134 caps = gst_pad_get_pad_template_caps (pad);
1136 GstCaps *intersection;
1139 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1140 gst_caps_unref (caps);
1141 caps = intersection;
1145 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1151 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1152 PangoFontDescription * desc)
1154 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1155 overlay->shadow_offset = (double) (font_size) / 13.0;
1156 overlay->outline_offset = (double) (font_size) / 15.0;
1157 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1158 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1162 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1163 gint * xpos, gint * ypos)
1166 GstBaseTextOverlayVAlign valign;
1167 GstBaseTextOverlayHAlign halign;
1169 width = overlay->image_width;
1170 height = overlay->image_height;
1172 if (overlay->use_vertical_render)
1173 halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1175 halign = overlay->halign;
1178 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1179 *xpos = overlay->xpad;
1181 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1182 *xpos = (overlay->width - width) / 2;
1184 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1185 *xpos = overlay->width - width - overlay->xpad;
1187 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1188 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1189 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1196 *xpos += overlay->deltax;
1198 if (overlay->use_vertical_render)
1199 valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1201 valign = overlay->valign;
1204 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1205 *ypos = overlay->height - height - overlay->ypad;
1207 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1208 *ypos = overlay->height - (height + overlay->ypad);
1210 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1211 *ypos = overlay->ypad;
1213 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1214 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1215 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1217 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1218 *ypos = (overlay->height - height) / 2;
1221 *ypos = overlay->ypad;
1224 *ypos += overlay->deltay;
1228 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1231 GstVideoOverlayRectangle *rectangle;
1233 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1235 if (overlay->text_image) {
1236 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1237 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1238 overlay->image_width, overlay->image_height);
1239 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1240 xpos, ypos, overlay->image_width, overlay->image_height,
1241 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1243 if (overlay->composition)
1244 gst_video_overlay_composition_unref (overlay->composition);
1245 overlay->composition = gst_video_overlay_composition_new (rectangle);
1246 gst_video_overlay_rectangle_unref (rectangle);
1248 } else if (overlay->composition) {
1249 gst_video_overlay_composition_unref (overlay->composition);
1250 overlay->composition = NULL;
1255 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1256 const gchar * string, gint textlen)
1259 cairo_surface_t *surface;
1260 PangoRectangle ink_rect, logical_rect;
1261 cairo_matrix_t cairo_matrix;
1263 double scalef = 1.0;
1268 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1270 if (overlay->auto_adjust_size) {
1271 /* 640 pixel is default */
1272 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1274 pango_layout_set_width (overlay->layout, -1);
1275 /* set text on pango layout */
1276 pango_layout_set_markup (overlay->layout, string, textlen);
1278 /* get subtitle image size */
1279 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1281 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1283 if (width + overlay->deltax >
1284 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1286 * subtitle image width is larger then overlay width
1287 * so rearrange overlay wrap mode.
1289 gst_base_text_overlay_update_wrap_mode (overlay);
1290 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1291 width = overlay->width;
1295 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1296 if (height > overlay->height) {
1297 height = overlay->height;
1299 if (overlay->use_vertical_render) {
1300 PangoRectangle rect;
1301 PangoContext *context;
1302 PangoMatrix matrix = PANGO_MATRIX_INIT;
1305 context = pango_layout_get_context (overlay->layout);
1307 pango_matrix_rotate (&matrix, -90);
1309 rect.x = rect.y = 0;
1311 rect.height = height;
1312 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1313 matrix.x0 = -rect.x;
1314 matrix.y0 = -rect.y;
1316 pango_context_set_matrix (context, &matrix);
1318 cairo_matrix.xx = matrix.xx;
1319 cairo_matrix.yx = matrix.yx;
1320 cairo_matrix.xy = matrix.xy;
1321 cairo_matrix.yy = matrix.yy;
1322 cairo_matrix.x0 = matrix.x0;
1323 cairo_matrix.y0 = matrix.y0;
1324 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1330 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1333 /* reallocate overlay buffer */
1334 buffer = gst_buffer_new_and_alloc (4 * width * height);
1335 gst_buffer_replace (&overlay->text_image, buffer);
1336 gst_buffer_unref (buffer);
1338 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1339 surface = cairo_image_surface_create_for_data (map.data,
1340 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1341 cr = cairo_create (surface);
1344 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1347 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1349 if (overlay->want_shading)
1350 cairo_paint_with_alpha (cr, overlay->shading_value);
1352 /* apply transformations */
1353 cairo_set_matrix (cr, &cairo_matrix);
1355 /* FIXME: We use show_layout everywhere except for the surface
1356 * because it's really faster and internally does all kinds of
1357 * caching. Unfortunately we have to paint to a cairo path for
1358 * the outline and this is slow. Once Pango supports user fonts
1359 * we should use them, see
1360 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1362 * Idea would the be, to create a cairo user font that
1363 * does shadow, outline, text painting in the
1364 * render_glyph function.
1367 /* draw shadow text */
1369 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1370 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1371 pango_cairo_show_layout (cr, overlay->layout);
1374 a = (overlay->outline_color >> 24) & 0xff;
1375 r = (overlay->outline_color >> 16) & 0xff;
1376 g = (overlay->outline_color >> 8) & 0xff;
1377 b = (overlay->outline_color >> 0) & 0xff;
1379 /* draw outline text */
1381 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1382 cairo_set_line_width (cr, overlay->outline_offset);
1383 pango_cairo_layout_path (cr, overlay->layout);
1387 a = (overlay->color >> 24) & 0xff;
1388 r = (overlay->color >> 16) & 0xff;
1389 g = (overlay->color >> 8) & 0xff;
1390 b = (overlay->color >> 0) & 0xff;
1394 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1395 pango_cairo_show_layout (cr, overlay->layout);
1399 cairo_surface_destroy (surface);
1400 gst_buffer_unmap (buffer, &map);
1401 overlay->image_width = width;
1402 overlay->image_height = height;
1403 overlay->baseline_y = ink_rect.y;
1404 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1406 gst_base_text_overlay_set_composition (overlay);
1413 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1414 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1416 gint i, j, dest_stride;
1419 dest_stride = dest->info.stride[0];
1420 dest_ptr = dest->data[0];
1422 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1423 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1425 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1426 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1428 for (i = y0; i < y1; ++i) {
1429 for (j = x0; j < x1; ++j) {
1430 gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1432 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1438 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1439 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1442 guint dest_stride, pixel_stride;
1445 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1446 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1447 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1449 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1450 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1452 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1453 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1456 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1458 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1461 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1463 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1465 for (i = y0; i < y1; i++) {
1466 for (j = x0; j < x1; j++) {
1470 y_pos = (i * dest_stride) + j * pixel_stride;
1471 y = dest_ptr[y_pos] + overlay->shading_value;
1473 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1478 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1479 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1480 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1482 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1483 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1488 dest_ptr = dest->data[0];
1490 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1491 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1493 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1494 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1496 for (i = y0; i < y1; i++) {
1497 for (j = x0; j < x1; j++) {
1500 y_pos = (i * 4 * overlay->width) + j * 4;
1501 for (k = 0; k < 4; k++) {
1502 y = dest_ptr[y_pos + k] + overlay->shading_value;
1503 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1509 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1510 static inline void \
1511 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1512 gint x0, gint x1, gint y0, gint y1) \
1517 dest_ptr = dest->data[0];\
1519 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1520 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1522 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1523 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1525 for (i = y0; i < y1; i++) {\
1526 for (j = x0; j < x1; j++) {\
1528 y_pos = (i * 4 * overlay->width) + j * 4;\
1529 for (k = OFFSET; k < 3+OFFSET; k++) {\
1530 y = dest_ptr[y_pos + k] + overlay->shading_value;\
1531 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1536 ARGB_SHADE_FUNCTION (ARGB, 1);
1537 ARGB_SHADE_FUNCTION (ABGR, 1);
1538 ARGB_SHADE_FUNCTION (RGBA, 0);
1539 ARGB_SHADE_FUNCTION (BGRA, 0);
1542 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1543 const gchar * text, gint textlen)
1547 if (!overlay->need_render) {
1548 GST_DEBUG ("Using previously rendered text.");
1552 /* -1 is the whole string */
1553 if (text != NULL && textlen < 0) {
1554 textlen = strlen (text);
1558 string = g_strndup (text, textlen);
1559 } else { /* empty string */
1560 string = g_strdup (" ");
1562 g_strdelimit (string, "\r\t", ' ');
1563 textlen = strlen (string);
1565 /* FIXME: should we check for UTF-8 here? */
1567 GST_DEBUG ("Rendering '%s'", string);
1568 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1572 overlay->need_render = FALSE;
1575 static GstFlowReturn
1576 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1577 GstBuffer * video_frame)
1580 GstVideoFrame frame;
1582 if (overlay->composition == NULL)
1585 if (gst_pad_check_reconfigure (overlay->srcpad))
1586 gst_base_text_overlay_negotiate (overlay);
1588 video_frame = gst_buffer_make_writable (video_frame);
1590 if (overlay->attach_compo_to_buffer) {
1591 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1592 gst_buffer_add_video_overlay_composition_meta (video_frame,
1593 overlay->composition);
1594 /* FIXME: emulate shaded background box if want_shading=true */
1598 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1602 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1604 /* shaded background box */
1605 if (overlay->want_shading) {
1606 switch (overlay->format) {
1607 case GST_VIDEO_FORMAT_I420:
1608 case GST_VIDEO_FORMAT_NV12:
1609 case GST_VIDEO_FORMAT_NV21:
1610 gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1611 xpos, xpos + overlay->image_width,
1612 ypos, ypos + overlay->image_height);
1614 case GST_VIDEO_FORMAT_AYUV:
1615 case GST_VIDEO_FORMAT_UYVY:
1616 gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1617 xpos, xpos + overlay->image_width,
1618 ypos, ypos + overlay->image_height);
1620 case GST_VIDEO_FORMAT_xRGB:
1621 gst_base_text_overlay_shade_xRGB (overlay, &frame,
1622 xpos, xpos + overlay->image_width,
1623 ypos, ypos + overlay->image_height);
1625 case GST_VIDEO_FORMAT_xBGR:
1626 gst_base_text_overlay_shade_xBGR (overlay, &frame,
1627 xpos, xpos + overlay->image_width,
1628 ypos, ypos + overlay->image_height);
1630 case GST_VIDEO_FORMAT_BGRx:
1631 gst_base_text_overlay_shade_BGRx (overlay, &frame,
1632 xpos, xpos + overlay->image_width,
1633 ypos, ypos + overlay->image_height);
1635 case GST_VIDEO_FORMAT_RGBx:
1636 gst_base_text_overlay_shade_RGBx (overlay, &frame,
1637 xpos, xpos + overlay->image_width,
1638 ypos, ypos + overlay->image_height);
1640 case GST_VIDEO_FORMAT_ARGB:
1641 gst_base_text_overlay_shade_ARGB (overlay, &frame,
1642 xpos, xpos + overlay->image_width,
1643 ypos, ypos + overlay->image_height);
1645 case GST_VIDEO_FORMAT_ABGR:
1646 gst_base_text_overlay_shade_ABGR (overlay, &frame,
1647 xpos, xpos + overlay->image_width,
1648 ypos, ypos + overlay->image_height);
1650 case GST_VIDEO_FORMAT_RGBA:
1651 gst_base_text_overlay_shade_RGBA (overlay, &frame,
1652 xpos, xpos + overlay->image_width,
1653 ypos, ypos + overlay->image_height);
1655 case GST_VIDEO_FORMAT_BGRA:
1656 gst_base_text_overlay_shade_BGRA (overlay, &frame,
1657 xpos, xpos + overlay->image_width,
1658 ypos, ypos + overlay->image_height);
1661 g_assert_not_reached ();
1665 gst_video_overlay_composition_blend (overlay->composition, &frame);
1667 gst_video_frame_unmap (&frame);
1671 return gst_pad_push (overlay->srcpad, video_frame);
1676 gst_buffer_unref (video_frame);
1677 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1682 static GstPadLinkReturn
1683 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1685 GstBaseTextOverlay *overlay;
1687 overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1688 if (G_UNLIKELY (!overlay))
1689 return GST_PAD_LINK_REFUSED;
1691 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1693 overlay->text_linked = TRUE;
1695 gst_object_unref (overlay);
1697 return GST_PAD_LINK_OK;
1701 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
1703 GstBaseTextOverlay *overlay;
1705 /* don't use gst_pad_get_parent() here, will deadlock */
1706 overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1708 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1710 overlay->text_linked = FALSE;
1712 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1716 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1719 gboolean ret = FALSE;
1720 GstBaseTextOverlay *overlay = NULL;
1722 overlay = GST_BASE_TEXT_OVERLAY (parent);
1724 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1726 switch (GST_EVENT_TYPE (event)) {
1727 case GST_EVENT_CAPS:
1731 gst_event_parse_caps (event, &caps);
1732 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1733 gst_event_unref (event);
1736 case GST_EVENT_SEGMENT:
1738 const GstSegment *segment;
1740 overlay->text_eos = FALSE;
1742 gst_event_parse_segment (event, &segment);
1744 if (segment->format == GST_FORMAT_TIME) {
1745 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1746 gst_segment_copy_into (segment, &overlay->text_segment);
1747 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1748 &overlay->text_segment);
1749 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1751 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1752 ("received non-TIME newsegment event on text input"));
1755 gst_event_unref (event);
1758 /* wake up the video chain, it might be waiting for a text buffer or
1759 * a text segment update */
1760 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1761 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1762 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1767 GstClockTime start, duration;
1769 gst_event_parse_gap (event, &start, &duration);
1770 if (GST_CLOCK_TIME_IS_VALID (duration))
1772 /* we do not expect another buffer until after gap,
1773 * so that is our position now */
1774 overlay->text_segment.position = start;
1776 /* wake up the video chain, it might be waiting for a text buffer or
1777 * a text segment update */
1778 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1779 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1780 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1782 case GST_EVENT_FLUSH_STOP:
1783 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1784 GST_INFO_OBJECT (overlay, "text flush stop");
1785 overlay->text_flushing = FALSE;
1786 overlay->text_eos = FALSE;
1787 gst_base_text_overlay_pop_text (overlay);
1788 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1789 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1790 gst_event_unref (event);
1793 case GST_EVENT_FLUSH_START:
1794 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1795 GST_INFO_OBJECT (overlay, "text flush start");
1796 overlay->text_flushing = TRUE;
1797 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1798 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1799 gst_event_unref (event);
1803 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1804 overlay->text_eos = TRUE;
1805 GST_INFO_OBJECT (overlay, "text EOS");
1806 /* wake up the video chain, it might be waiting for a text buffer or
1807 * a text segment update */
1808 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1809 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1810 gst_event_unref (event);
1814 ret = gst_pad_event_default (pad, parent, event);
1822 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1825 gboolean ret = FALSE;
1826 GstBaseTextOverlay *overlay = NULL;
1828 overlay = GST_BASE_TEXT_OVERLAY (parent);
1830 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1832 switch (GST_EVENT_TYPE (event)) {
1833 case GST_EVENT_CAPS:
1837 gst_event_parse_caps (event, &caps);
1838 ret = gst_base_text_overlay_setcaps (overlay, caps);
1839 gst_event_unref (event);
1842 case GST_EVENT_SEGMENT:
1844 const GstSegment *segment;
1846 GST_DEBUG_OBJECT (overlay, "received new segment");
1848 gst_event_parse_segment (event, &segment);
1850 if (segment->format == GST_FORMAT_TIME) {
1851 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1854 gst_segment_copy_into (segment, &overlay->segment);
1856 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1857 ("received non-TIME newsegment event on video input"));
1860 ret = gst_pad_event_default (pad, parent, event);
1864 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1865 GST_INFO_OBJECT (overlay, "video EOS");
1866 overlay->video_eos = TRUE;
1867 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1868 ret = gst_pad_event_default (pad, parent, event);
1870 case GST_EVENT_FLUSH_START:
1871 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1872 GST_INFO_OBJECT (overlay, "video flush start");
1873 overlay->video_flushing = TRUE;
1874 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1875 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1876 ret = gst_pad_event_default (pad, parent, event);
1878 case GST_EVENT_FLUSH_STOP:
1879 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1880 GST_INFO_OBJECT (overlay, "video flush stop");
1881 overlay->video_flushing = FALSE;
1882 overlay->video_eos = FALSE;
1883 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1884 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1885 ret = gst_pad_event_default (pad, parent, event);
1888 ret = gst_pad_event_default (pad, parent, event);
1896 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1899 gboolean ret = FALSE;
1900 GstBaseTextOverlay *overlay;
1902 overlay = GST_BASE_TEXT_OVERLAY (parent);
1904 switch (GST_QUERY_TYPE (query)) {
1905 case GST_QUERY_CAPS:
1907 GstCaps *filter, *caps;
1909 gst_query_parse_caps (query, &filter);
1910 caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1911 gst_query_set_caps_result (query, caps);
1912 gst_caps_unref (caps);
1917 ret = gst_pad_query_default (pad, parent, query);
1924 /* Called with lock held */
1926 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1928 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1930 if (overlay->text_buffer) {
1931 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1932 overlay->text_buffer);
1933 gst_buffer_unref (overlay->text_buffer);
1934 overlay->text_buffer = NULL;
1937 /* Let the text task know we used that buffer */
1938 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1941 /* We receive text buffers here. If they are out of segment we just ignore them.
1942 If the buffer is in our segment we keep it internally except if another one
1943 is already waiting here, in that case we wait that it gets kicked out */
1944 static GstFlowReturn
1945 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1948 GstFlowReturn ret = GST_FLOW_OK;
1949 GstBaseTextOverlay *overlay = NULL;
1950 gboolean in_seg = FALSE;
1951 guint64 clip_start = 0, clip_stop = 0;
1953 overlay = GST_BASE_TEXT_OVERLAY (parent);
1955 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1957 if (overlay->text_flushing) {
1958 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1959 ret = GST_FLOW_FLUSHING;
1960 GST_LOG_OBJECT (overlay, "text flushing");
1964 if (overlay->text_eos) {
1965 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1967 GST_LOG_OBJECT (overlay, "text EOS");
1971 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1972 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1973 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1974 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1975 GST_BUFFER_DURATION (buffer)));
1977 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1980 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1981 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1983 stop = GST_CLOCK_TIME_NONE;
1985 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
1986 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1992 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1993 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1994 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1995 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1997 /* Wait for the previous buffer to go away */
1998 while (overlay->text_buffer != NULL) {
1999 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2000 GST_DEBUG_PAD_NAME (pad));
2001 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2002 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2003 if (overlay->text_flushing) {
2004 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2005 ret = GST_FLOW_FLUSHING;
2010 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2011 overlay->text_segment.position = clip_start;
2013 overlay->text_buffer = buffer;
2014 /* That's a new text buffer we need to render */
2015 overlay->need_render = TRUE;
2017 /* in case the video chain is waiting for a text buffer, wake it up */
2018 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2021 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2028 static GstFlowReturn
2029 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2032 GstBaseTextOverlayClass *klass;
2033 GstBaseTextOverlay *overlay;
2034 GstFlowReturn ret = GST_FLOW_OK;
2035 gboolean in_seg = FALSE;
2036 guint64 start, stop, clip_start = 0, clip_stop = 0;
2039 overlay = GST_BASE_TEXT_OVERLAY (parent);
2040 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2042 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2043 goto missing_timestamp;
2045 /* ignore buffers that are outside of the current segment */
2046 start = GST_BUFFER_TIMESTAMP (buffer);
2048 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2049 stop = GST_CLOCK_TIME_NONE;
2051 stop = start + GST_BUFFER_DURATION (buffer);
2054 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2055 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2056 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2058 /* segment_clip() will adjust start unconditionally to segment_start if
2059 * no stop time is provided, so handle this ourselves */
2060 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2061 goto out_of_segment;
2063 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2064 &clip_start, &clip_stop);
2067 goto out_of_segment;
2069 /* if the buffer is only partially in the segment, fix up stamps */
2070 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2071 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2072 buffer = gst_buffer_make_writable (buffer);
2073 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2075 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2078 /* now, after we've done the clipping, fix up end time if there's no
2079 * duration (we only use those estimated values internally though, we
2080 * don't want to set bogus values on the buffer itself) */
2084 gint fps_num, fps_denom;
2086 /* FIXME, store this in setcaps */
2087 caps = gst_pad_get_current_caps (pad);
2088 s = gst_caps_get_structure (caps, 0);
2089 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2090 fps_num && fps_denom) {
2091 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2092 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2094 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2095 stop = start + 1; /* we need to assume some interval */
2097 gst_caps_unref (caps);
2100 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2104 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2106 if (overlay->video_flushing)
2109 if (overlay->video_eos)
2112 if (overlay->silent) {
2113 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2114 ret = gst_pad_push (overlay->srcpad, buffer);
2116 /* Update position */
2117 overlay->segment.position = clip_start;
2122 /* Text pad not linked, rendering internal text */
2123 if (!overlay->text_linked) {
2124 if (klass->get_text) {
2125 text = klass->get_text (overlay, buffer);
2127 text = g_strdup (overlay->default_text);
2130 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2131 "text: '%s'", GST_STR_NULL (text));
2133 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2135 if (text != NULL && *text != '\0') {
2136 /* Render and push */
2137 gst_base_text_overlay_render_text (overlay, text, -1);
2138 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2140 /* Invalid or empty string */
2141 ret = gst_pad_push (overlay->srcpad, buffer);
2144 /* Text pad linked, check if we have a text buffer queued */
2145 if (overlay->text_buffer) {
2146 gboolean pop_text = FALSE, valid_text_time = TRUE;
2147 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2148 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2149 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2150 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2151 GstClockTime vid_running_time, vid_running_time_end;
2153 /* if the text buffer isn't stamped right, pop it off the
2154 * queue and display it for the current video frame only */
2155 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2156 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2157 GST_WARNING_OBJECT (overlay,
2158 "Got text buffer with invalid timestamp or duration");
2160 valid_text_time = FALSE;
2162 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2163 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2167 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2169 vid_running_time_end =
2170 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2173 /* If timestamp and duration are valid */
2174 if (valid_text_time) {
2176 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2178 text_running_time_end =
2179 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2183 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2184 GST_TIME_ARGS (text_running_time),
2185 GST_TIME_ARGS (text_running_time_end));
2186 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2187 GST_TIME_ARGS (vid_running_time),
2188 GST_TIME_ARGS (vid_running_time_end));
2190 /* Text too old or in the future */
2191 if (valid_text_time && text_running_time_end <= vid_running_time) {
2192 /* text buffer too old, get rid of it and do nothing */
2193 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2195 gst_base_text_overlay_pop_text (overlay);
2196 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2197 goto wait_for_text_buf;
2198 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2199 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2200 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2201 /* Push the video frame */
2202 ret = gst_pad_push (overlay->srcpad, buffer);
2208 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2209 in_text = (gchar *) map.data;
2212 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2213 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2214 * here on purpose, this is something that needs fixing upstream */
2215 if (!g_utf8_validate (in_text, in_size, NULL)) {
2216 const gchar *end = NULL;
2218 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2219 in_text = g_strndup (in_text, in_size);
2220 while (!g_utf8_validate (in_text, in_size, &end) && end)
2221 *((gchar *) end) = '*';
2224 /* Get the string */
2225 if (overlay->have_pango_markup) {
2226 text = g_strndup (in_text, in_size);
2228 text = g_markup_escape_text (in_text, in_size);
2231 if (text != NULL && *text != '\0') {
2232 gint text_len = strlen (text);
2234 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2235 text[text_len - 1] == '\r')) {
2238 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2239 gst_base_text_overlay_render_text (overlay, text, text_len);
2241 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2242 gst_base_text_overlay_render_text (overlay, " ", 1);
2244 if (in_text != (gchar *) map.data)
2247 gst_buffer_unmap (overlay->text_buffer, &map);
2249 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2250 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2252 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2253 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2258 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2259 gst_base_text_overlay_pop_text (overlay);
2260 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2263 gboolean wait_for_text_buf = TRUE;
2265 if (overlay->text_eos)
2266 wait_for_text_buf = FALSE;
2268 if (!overlay->wait_text)
2269 wait_for_text_buf = FALSE;
2271 /* Text pad linked, but no text buffer available - what now? */
2272 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2273 GstClockTime text_start_running_time, text_position_running_time;
2274 GstClockTime vid_running_time;
2277 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2278 GST_BUFFER_TIMESTAMP (buffer));
2279 text_start_running_time =
2280 gst_segment_to_running_time (&overlay->text_segment,
2281 GST_FORMAT_TIME, overlay->text_segment.start);
2282 text_position_running_time =
2283 gst_segment_to_running_time (&overlay->text_segment,
2284 GST_FORMAT_TIME, overlay->text_segment.position);
2286 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2287 vid_running_time < text_start_running_time) ||
2288 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2289 vid_running_time < text_position_running_time)) {
2290 wait_for_text_buf = FALSE;
2294 if (wait_for_text_buf) {
2295 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2296 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2297 GST_DEBUG_OBJECT (overlay, "resuming");
2298 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2299 goto wait_for_text_buf;
2301 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2302 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2303 ret = gst_pad_push (overlay->srcpad, buffer);
2310 /* Update position */
2311 overlay->segment.position = clip_start;
2317 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2318 gst_buffer_unref (buffer);
2324 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2325 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2326 gst_buffer_unref (buffer);
2327 return GST_FLOW_FLUSHING;
2331 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2332 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2333 gst_buffer_unref (buffer);
2334 return GST_FLOW_EOS;
2338 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2339 gst_buffer_unref (buffer);
2344 static GstStateChangeReturn
2345 gst_base_text_overlay_change_state (GstElement * element,
2346 GstStateChange transition)
2348 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2349 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2351 switch (transition) {
2352 case GST_STATE_CHANGE_PAUSED_TO_READY:
2353 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2354 overlay->text_flushing = TRUE;
2355 overlay->video_flushing = TRUE;
2356 /* pop_text will broadcast on the GCond and thus also make the video
2357 * chain exit if it's waiting for a text buffer */
2358 gst_base_text_overlay_pop_text (overlay);
2359 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2365 ret = parent_class->change_state (element, transition);
2366 if (ret == GST_STATE_CHANGE_FAILURE)
2369 switch (transition) {
2370 case GST_STATE_CHANGE_READY_TO_PAUSED:
2371 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2372 overlay->text_flushing = FALSE;
2373 overlay->video_flushing = FALSE;
2374 overlay->video_eos = FALSE;
2375 overlay->text_eos = FALSE;
2376 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2377 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2378 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2388 plugin_init (GstPlugin * plugin)
2390 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2391 GST_TYPE_TEXT_OVERLAY) ||
2392 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2393 GST_TYPE_TIME_OVERLAY) ||
2394 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2395 GST_TYPE_CLOCK_OVERLAY) ||
2396 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2397 GST_TYPE_TEXT_RENDER)) {
2401 /*texttestsrc_plugin_init(module, plugin); */
2403 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2408 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2409 pango, "Pango-based text rendering and overlay", plugin_init,
2410 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)