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)
68 * One can also feed arbitrary live text into the element:
70 * gst-launch fdsrc fd=0 ! text/plain ! txt. videotestsrc ! \
71 * textoverlay name=txt shaded-background=yes font-desc="Serif 40" wait-text=false ! \
73 * ]| This shows new text as entered on the terminal (stdin). This is not suited
74 * for subtitles as the test overlay is not timed. Subtitles should use
75 * timestamped formats. For the above use case one can also read the text from
76 * the application as set the #GstTextOverlay:text property.
81 /* FIXME: alloc segment as part of instance struct */
87 #include <gst/video/video.h>
89 #include "gsttextoverlay.h"
90 #include "gsttimeoverlay.h"
91 #include "gstclockoverlay.h"
92 #include "gsttextrender.h"
95 #include "gst/glib-compat-private.h"
98 * - use proper strides and offset for I420
99 * - if text is wider than the video picture, it does not get
100 * clipped properly during blitting (if wrapping is disabled)
101 * - make 'shading_value' a property (or enum: light/normal/dark/verydark)?
104 GST_DEBUG_CATEGORY (pango_debug);
105 #define GST_CAT_DEFAULT pango_debug
107 #define DEFAULT_PROP_TEXT ""
108 #define DEFAULT_PROP_SHADING FALSE
109 #define DEFAULT_PROP_SHADOW TRUE
110 #define DEFAULT_PROP_VALIGNMENT GST_TEXT_OVERLAY_VALIGN_BASELINE
111 #define DEFAULT_PROP_HALIGNMENT GST_TEXT_OVERLAY_HALIGN_CENTER
112 #define DEFAULT_PROP_VALIGN "baseline"
113 #define DEFAULT_PROP_HALIGN "center"
114 #define DEFAULT_PROP_XPAD 25
115 #define DEFAULT_PROP_YPAD 25
116 #define DEFAULT_PROP_DELTAX 0
117 #define DEFAULT_PROP_DELTAY 0
118 #define DEFAULT_PROP_XPOS 0.5
119 #define DEFAULT_PROP_YPOS 0.5
120 #define DEFAULT_PROP_WRAP_MODE GST_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
121 #define DEFAULT_PROP_FONT_DESC ""
122 #define DEFAULT_PROP_SILENT FALSE
123 #define DEFAULT_PROP_LINE_ALIGNMENT GST_TEXT_OVERLAY_LINE_ALIGN_CENTER
124 #define DEFAULT_PROP_WAIT_TEXT TRUE
125 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
126 #define DEFAULT_PROP_VERTICAL_RENDER FALSE
127 #define DEFAULT_PROP_COLOR 0xffffffff
128 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
130 /* make a property of me */
131 #define DEFAULT_SHADING_VALUE -80
133 #define MINIMUM_OUTLINE_OFFSET 1.0
134 #define DEFAULT_SCALE_BASIS 640
136 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
137 # define CAIRO_ARGB_A 3
138 # define CAIRO_ARGB_R 2
139 # define CAIRO_ARGB_G 1
140 # define CAIRO_ARGB_B 0
142 # define CAIRO_ARGB_A 0
143 # define CAIRO_ARGB_R 1
144 # define CAIRO_ARGB_G 2
145 # define CAIRO_ARGB_B 3
153 PROP_VALIGN, /* deprecated */
154 PROP_HALIGN, /* deprecated */
168 PROP_AUTO_ADJUST_SIZE,
169 PROP_VERTICAL_RENDER,
176 /* FIXME Use GST_VIDEO_CAPS_SURFACE when it lands in base */
177 static GstStaticPadTemplate src_template_factory =
178 GST_STATIC_PAD_TEMPLATE ("src",
181 GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx ";"
182 GST_VIDEO_CAPS_RGB ";"
183 GST_VIDEO_CAPS_BGR ";"
184 GST_VIDEO_CAPS_RGBx ";"
185 GST_VIDEO_CAPS_xRGB ";"
186 GST_VIDEO_CAPS_xBGR ";"
187 GST_VIDEO_CAPS_RGBA ";"
188 GST_VIDEO_CAPS_BGRA ";"
189 GST_VIDEO_CAPS_ARGB ";"
190 GST_VIDEO_CAPS_ABGR ";"
192 GST_VIDEO_CAPS_YUV ("{I420, YV12, AYUV, YUY2, UYVY, v308, v210,"
193 " v216, Y41B, Y42B, Y444, Y800, Y16, NV12, NV21, UYVP, A420,"
197 static GstStaticPadTemplate video_sink_template_factory =
198 GST_STATIC_PAD_TEMPLATE ("video_sink",
201 GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx ";"
202 GST_VIDEO_CAPS_RGB ";"
203 GST_VIDEO_CAPS_BGR ";"
204 GST_VIDEO_CAPS_RGBx ";"
205 GST_VIDEO_CAPS_xRGB ";"
206 GST_VIDEO_CAPS_xBGR ";"
207 GST_VIDEO_CAPS_RGBA ";"
208 GST_VIDEO_CAPS_BGRA ";"
209 GST_VIDEO_CAPS_ARGB ";"
210 GST_VIDEO_CAPS_ABGR ";"
212 GST_VIDEO_CAPS_YUV ("{I420, YV12, AYUV, YUY2, UYVY, v308, v210,"
213 " v216, Y41B, Y42B, Y444, Y800, Y16, NV12, NV21, UYVP, A420,"
217 static GstStaticPadTemplate text_sink_template_factory =
218 GST_STATIC_PAD_TEMPLATE ("text_sink",
221 GST_STATIC_CAPS ("text/x-pango-markup; text/plain")
224 #define GST_TYPE_TEXT_OVERLAY_VALIGN (gst_text_overlay_valign_get_type())
226 gst_text_overlay_valign_get_type (void)
228 static GType text_overlay_valign_type = 0;
229 static const GEnumValue text_overlay_valign[] = {
230 {GST_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
231 {GST_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
232 {GST_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
233 {GST_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
234 {GST_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
238 if (!text_overlay_valign_type) {
239 text_overlay_valign_type =
240 g_enum_register_static ("GstTextOverlayVAlign", text_overlay_valign);
242 return text_overlay_valign_type;
245 #define GST_TYPE_TEXT_OVERLAY_HALIGN (gst_text_overlay_halign_get_type())
247 gst_text_overlay_halign_get_type (void)
249 static GType text_overlay_halign_type = 0;
250 static const GEnumValue text_overlay_halign[] = {
251 {GST_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
252 {GST_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
253 {GST_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
254 {GST_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
258 if (!text_overlay_halign_type) {
259 text_overlay_halign_type =
260 g_enum_register_static ("GstTextOverlayHAlign", text_overlay_halign);
262 return text_overlay_halign_type;
266 #define GST_TYPE_TEXT_OVERLAY_WRAP_MODE (gst_text_overlay_wrap_mode_get_type())
268 gst_text_overlay_wrap_mode_get_type (void)
270 static GType text_overlay_wrap_mode_type = 0;
271 static const GEnumValue text_overlay_wrap_mode[] = {
272 {GST_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
273 {GST_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
274 {GST_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
275 {GST_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
279 if (!text_overlay_wrap_mode_type) {
280 text_overlay_wrap_mode_type =
281 g_enum_register_static ("GstTextOverlayWrapMode",
282 text_overlay_wrap_mode);
284 return text_overlay_wrap_mode_type;
287 #define GST_TYPE_TEXT_OVERLAY_LINE_ALIGN (gst_text_overlay_line_align_get_type())
289 gst_text_overlay_line_align_get_type (void)
291 static GType text_overlay_line_align_type = 0;
292 static const GEnumValue text_overlay_line_align[] = {
293 {GST_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
294 {GST_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
295 {GST_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
299 if (!text_overlay_line_align_type) {
300 text_overlay_line_align_type =
301 g_enum_register_static ("GstTextOverlayLineAlign",
302 text_overlay_line_align);
304 return text_overlay_line_align_type;
307 #define GST_TEXT_OVERLAY_GET_COND(ov) (((GstTextOverlay *)ov)->cond)
308 #define GST_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_TEXT_OVERLAY_GET_COND (ov), GST_OBJECT_GET_LOCK (ov)))
309 #define GST_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_TEXT_OVERLAY_GET_COND (ov)))
310 #define GST_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_TEXT_OVERLAY_GET_COND (ov)))
312 static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element,
313 GstStateChange transition);
315 static GstCaps *gst_text_overlay_getcaps (GstPad * pad);
316 static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
317 static gboolean gst_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps);
318 static gboolean gst_text_overlay_src_event (GstPad * pad, GstEvent * event);
319 static gboolean gst_text_overlay_src_query (GstPad * pad, GstQuery * query);
321 static gboolean gst_text_overlay_video_event (GstPad * pad, GstEvent * event);
322 static GstFlowReturn gst_text_overlay_video_chain (GstPad * pad,
324 static GstFlowReturn gst_text_overlay_video_bufferalloc (GstPad * pad,
325 guint64 offset, guint size, GstCaps * caps, GstBuffer ** buffer);
327 static gboolean gst_text_overlay_text_event (GstPad * pad, GstEvent * event);
328 static GstFlowReturn gst_text_overlay_text_chain (GstPad * pad,
330 static GstPadLinkReturn gst_text_overlay_text_pad_link (GstPad * pad,
332 static void gst_text_overlay_text_pad_unlink (GstPad * pad);
333 static void gst_text_overlay_pop_text (GstTextOverlay * overlay);
334 static void gst_text_overlay_update_render_mode (GstTextOverlay * overlay);
336 static void gst_text_overlay_finalize (GObject * object);
337 static void gst_text_overlay_set_property (GObject * object, guint prop_id,
338 const GValue * value, GParamSpec * pspec);
339 static void gst_text_overlay_get_property (GObject * object, guint prop_id,
340 GValue * value, GParamSpec * pspec);
341 static void gst_text_overlay_adjust_values_with_fontdesc (GstTextOverlay *
342 overlay, PangoFontDescription * desc);
344 GST_BOILERPLATE (GstTextOverlay, gst_text_overlay, GstElement,
348 gst_text_overlay_base_init (gpointer g_class)
350 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
351 GstTextOverlayClass *klass = GST_TEXT_OVERLAY_CLASS (g_class);
352 PangoFontMap *fontmap;
354 gst_element_class_add_static_pad_template (element_class,
355 &src_template_factory);
356 gst_element_class_add_static_pad_template (element_class,
357 &video_sink_template_factory);
360 if (!GST_IS_TIME_OVERLAY_CLASS (g_class) &&
361 !GST_IS_CLOCK_OVERLAY_CLASS (g_class)) {
362 gst_element_class_add_static_pad_template (element_class,
363 &text_sink_template_factory);
366 gst_element_class_set_details_simple (element_class, "Text overlay",
367 "Filter/Editor/Video",
368 "Adds text strings on top of a video buffer",
369 "David Schleef <ds@schleef.org>, " "Zeeshan Ali <zeeshan.ali@nokia.com>");
371 /* Only lock for the subclasses here, the base class
372 * doesn't have this mutex yet and it's not necessary
374 if (klass->pango_lock)
375 g_mutex_lock (klass->pango_lock);
376 fontmap = pango_cairo_font_map_get_default ();
377 klass->pango_context =
378 pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (fontmap));
379 if (klass->pango_lock)
380 g_mutex_unlock (klass->pango_lock);
384 gst_text_overlay_get_text (GstTextOverlay * overlay, GstBuffer * video_frame)
386 return g_strdup (overlay->default_text);
390 gst_text_overlay_class_init (GstTextOverlayClass * klass)
392 GObjectClass *gobject_class;
393 GstElementClass *gstelement_class;
395 gobject_class = (GObjectClass *) klass;
396 gstelement_class = (GstElementClass *) klass;
398 gobject_class->finalize = gst_text_overlay_finalize;
399 gobject_class->set_property = gst_text_overlay_set_property;
400 gobject_class->get_property = gst_text_overlay_get_property;
402 gstelement_class->change_state =
403 GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
405 klass->pango_lock = g_mutex_new ();
407 klass->get_text = gst_text_overlay_get_text;
409 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
410 g_param_spec_string ("text", "text",
411 "Text to be display.", DEFAULT_PROP_TEXT,
412 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
413 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
414 g_param_spec_boolean ("shaded-background", "shaded background",
415 "Whether to shade the background under the text area",
416 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
418 * GstTextOverlay:shadow
420 * Whether to display a shadow of each letter under the text.
424 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADOW,
425 g_param_spec_boolean ("shadow", "create shadow of text",
426 "Whether to create a shadow of the letters under the text",
427 DEFAULT_PROP_SHADOW, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
428 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
429 g_param_spec_enum ("valignment", "vertical alignment",
430 "Vertical alignment of the text", GST_TYPE_TEXT_OVERLAY_VALIGN,
431 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
432 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
433 g_param_spec_enum ("halignment", "horizontal alignment",
434 "Horizontal alignment of the text", GST_TYPE_TEXT_OVERLAY_HALIGN,
435 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
436 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGN,
437 g_param_spec_string ("valign", "vertical alignment",
438 "Vertical alignment of the text (deprecated; use valignment)",
439 DEFAULT_PROP_VALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
440 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGN,
441 g_param_spec_string ("halign", "horizontal alignment",
442 "Horizontal alignment of the text (deprecated; use halignment)",
443 DEFAULT_PROP_HALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
444 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
445 g_param_spec_int ("xpad", "horizontal paddding",
446 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
447 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
448 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
449 g_param_spec_int ("ypad", "vertical padding",
450 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
451 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
452 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
453 g_param_spec_int ("deltax", "X position modifier",
454 "Shift X position to the left or to the right. Unit is pixels.",
455 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
456 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
457 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
458 g_param_spec_int ("deltay", "Y position modifier",
459 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
460 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
462 * GstTextOverlay:xpos
464 * Horizontal position of the rendered text when using positioned alignment.
468 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
469 g_param_spec_double ("xpos", "horizontal position",
470 "Horizontal position when using position alignment", 0, 1.0,
472 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
474 * GstTextOverlay:ypos
476 * Vertical position of the rendered text when using positioned alignment.
480 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
481 g_param_spec_double ("ypos", "vertical position",
482 "Vertical position when using position alignment", 0, 1.0,
484 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
485 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
486 g_param_spec_enum ("wrap-mode", "wrap mode",
487 "Whether to wrap the text and if so how.",
488 GST_TYPE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
489 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
490 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
491 g_param_spec_string ("font-desc", "font description",
492 "Pango font description of font to be used for rendering. "
493 "See documentation of pango_font_description_from_string "
494 "for syntax.", DEFAULT_PROP_FONT_DESC,
495 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
497 * GstTextOverlay:color
499 * Color of the rendered text.
503 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
504 g_param_spec_uint ("color", "Color",
505 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
507 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
509 * GstTextOverlay:outline-color
511 * Color of the outline of the rendered text.
515 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
516 g_param_spec_uint ("outline-color", "Text Outline Color",
517 "Color to use for outline the text (big-endian ARGB).", 0,
518 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
519 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
522 * GstTextOverlay:line-alignment
524 * Alignment of text lines relative to each other (for multi-line text)
528 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
529 g_param_spec_enum ("line-alignment", "line alignment",
530 "Alignment of text lines relative to each other.",
531 GST_TYPE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
532 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
534 * GstTextOverlay:silent
536 * If set, no text is rendered. Useful to switch off text rendering
537 * temporarily without removing the textoverlay element from the pipeline.
541 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
542 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
543 g_param_spec_boolean ("silent", "silent",
544 "Whether to render the text string",
546 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
548 * GstTextOverlay:wait-text
550 * If set, the video will block until a subtitle is received on the text pad.
551 * If video and subtitles are sent in sync, like from the same demuxer, this
552 * property should be set.
556 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
557 g_param_spec_boolean ("wait-text", "Wait Text",
558 "Whether to wait for subtitles",
559 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
561 g_object_class_install_property (G_OBJECT_CLASS (klass),
562 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
563 "Automatically adjust font size to screen-size.",
564 DEFAULT_PROP_AUTO_ADJUST_SIZE,
565 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
567 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
568 g_param_spec_boolean ("vertical-render", "vertical render",
569 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
570 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
574 gst_text_overlay_finalize (GObject * object)
576 GstTextOverlay *overlay = GST_TEXT_OVERLAY (object);
578 g_free (overlay->default_text);
580 if (overlay->composition) {
581 gst_video_overlay_composition_unref (overlay->composition);
582 overlay->composition = NULL;
585 if (overlay->text_image) {
586 gst_buffer_unref (overlay->text_image);
587 overlay->text_image = NULL;
590 if (overlay->layout) {
591 g_object_unref (overlay->layout);
592 overlay->layout = NULL;
595 if (overlay->text_buffer) {
596 gst_buffer_unref (overlay->text_buffer);
597 overlay->text_buffer = NULL;
601 g_cond_free (overlay->cond);
602 overlay->cond = NULL;
605 G_OBJECT_CLASS (parent_class)->finalize (object);
609 gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * 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_getcaps_function (overlay->video_sinkpad,
619 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
620 gst_pad_set_setcaps_function (overlay->video_sinkpad,
621 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
622 gst_pad_set_event_function (overlay->video_sinkpad,
623 GST_DEBUG_FUNCPTR (gst_text_overlay_video_event));
624 gst_pad_set_chain_function (overlay->video_sinkpad,
625 GST_DEBUG_FUNCPTR (gst_text_overlay_video_chain));
626 gst_pad_set_bufferalloc_function (overlay->video_sinkpad,
627 GST_DEBUG_FUNCPTR (gst_text_overlay_video_bufferalloc));
628 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
630 if (!GST_IS_TIME_OVERLAY_CLASS (klass) && !GST_IS_CLOCK_OVERLAY_CLASS (klass)) {
632 template = gst_static_pad_template_get (&text_sink_template_factory);
633 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
634 gst_object_unref (template);
635 gst_pad_set_setcaps_function (overlay->text_sinkpad,
636 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps_txt));
637 gst_pad_set_event_function (overlay->text_sinkpad,
638 GST_DEBUG_FUNCPTR (gst_text_overlay_text_event));
639 gst_pad_set_chain_function (overlay->text_sinkpad,
640 GST_DEBUG_FUNCPTR (gst_text_overlay_text_chain));
641 gst_pad_set_link_function (overlay->text_sinkpad,
642 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_link));
643 gst_pad_set_unlink_function (overlay->text_sinkpad,
644 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlink));
645 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
649 template = gst_static_pad_template_get (&src_template_factory);
650 overlay->srcpad = gst_pad_new_from_template (template, "src");
651 gst_object_unref (template);
652 gst_pad_set_getcaps_function (overlay->srcpad,
653 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
654 gst_pad_set_event_function (overlay->srcpad,
655 GST_DEBUG_FUNCPTR (gst_text_overlay_src_event));
656 gst_pad_set_query_function (overlay->srcpad,
657 GST_DEBUG_FUNCPTR (gst_text_overlay_src_query));
658 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
660 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
661 g_mutex_lock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
663 pango_layout_new (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_context);
665 pango_context_get_font_description (GST_TEXT_OVERLAY_GET_CLASS
666 (overlay)->pango_context);
667 gst_text_overlay_adjust_values_with_fontdesc (overlay, desc);
669 overlay->color = DEFAULT_PROP_COLOR;
670 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
671 overlay->halign = DEFAULT_PROP_HALIGNMENT;
672 overlay->valign = DEFAULT_PROP_VALIGNMENT;
673 overlay->xpad = DEFAULT_PROP_XPAD;
674 overlay->ypad = DEFAULT_PROP_YPAD;
675 overlay->deltax = DEFAULT_PROP_DELTAX;
676 overlay->deltay = DEFAULT_PROP_DELTAY;
677 overlay->xpos = DEFAULT_PROP_XPOS;
678 overlay->ypos = DEFAULT_PROP_YPOS;
680 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
682 overlay->want_shading = DEFAULT_PROP_SHADING;
683 overlay->want_shadow = DEFAULT_PROP_SHADOW;
684 overlay->shading_value = DEFAULT_SHADING_VALUE;
685 overlay->silent = DEFAULT_PROP_SILENT;
686 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
687 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
689 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
690 overlay->need_render = TRUE;
691 overlay->composition = NULL;
692 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
693 gst_text_overlay_update_render_mode (overlay);
698 overlay->text_buffer = NULL;
699 overlay->text_linked = FALSE;
700 overlay->cond = g_cond_new ();
701 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
702 g_mutex_unlock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
706 gst_text_overlay_update_wrap_mode (GstTextOverlay * overlay)
708 if (overlay->wrap_mode == GST_TEXT_OVERLAY_WRAP_MODE_NONE) {
709 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
710 pango_layout_set_width (overlay->layout, -1);
714 if (overlay->auto_adjust_size) {
715 width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
716 if (overlay->use_vertical_render) {
717 width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
721 (overlay->use_vertical_render ? overlay->height : overlay->width) *
725 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
726 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
727 pango_layout_set_width (overlay->layout, width);
728 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
733 gst_text_overlay_update_render_mode (GstTextOverlay * overlay)
735 PangoMatrix matrix = PANGO_MATRIX_INIT;
736 PangoContext *context = pango_layout_get_context (overlay->layout);
738 if (overlay->use_vertical_render) {
739 pango_matrix_rotate (&matrix, -90);
740 pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
741 pango_context_set_matrix (context, &matrix);
742 pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
744 pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
745 pango_context_set_matrix (context, &matrix);
746 pango_layout_set_alignment (overlay->layout, overlay->line_align);
751 gst_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps)
753 GstTextOverlay *overlay;
754 GstStructure *structure;
756 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
757 if (G_UNLIKELY (!overlay))
760 structure = gst_caps_get_structure (caps, 0);
761 overlay->have_pango_markup =
762 gst_structure_has_name (structure, "text/x-pango-markup");
764 gst_object_unref (overlay);
769 /* FIXME: upstream nego (e.g. when the video window is resized) */
772 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
774 GstTextOverlay *overlay;
775 GstStructure *structure;
776 gboolean ret = FALSE;
779 if (!GST_PAD_IS_SINK (pad))
782 g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
784 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
785 if (G_UNLIKELY (!overlay))
790 structure = gst_caps_get_structure (caps, 0);
791 fps = gst_structure_get_value (structure, "framerate");
794 && gst_video_format_parse_caps (caps, &overlay->format, &overlay->width,
796 ret = gst_pad_set_caps (overlay->srcpad, caps);
799 overlay->fps_n = gst_value_get_fraction_numerator (fps);
800 overlay->fps_d = gst_value_get_fraction_denominator (fps);
803 GstStructure *structure;
805 GST_OBJECT_LOCK (overlay);
806 g_mutex_lock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
808 /* FIXME Use the query to the sink to do that when implemented */
809 /* Update wether to attach composition to buffer or do the composition
811 structure = gst_caps_get_structure (caps, 0);
812 if (gst_structure_has_name (structure, "video/x-surface"))
813 overlay->attach_compo_to_buffer = TRUE;
815 overlay->attach_compo_to_buffer = FALSE;
817 gst_text_overlay_update_wrap_mode (overlay);
818 g_mutex_unlock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
819 GST_OBJECT_UNLOCK (overlay);
822 gst_object_unref (overlay);
828 gst_text_overlay_set_property (GObject * object, guint prop_id,
829 const GValue * value, GParamSpec * pspec)
831 GstTextOverlay *overlay = GST_TEXT_OVERLAY (object);
833 GST_OBJECT_LOCK (overlay);
836 g_free (overlay->default_text);
837 overlay->default_text = g_value_dup_string (value);
838 overlay->need_render = TRUE;
841 overlay->want_shading = g_value_get_boolean (value);
844 overlay->want_shadow = g_value_get_boolean (value);
847 overlay->xpad = g_value_get_int (value);
850 overlay->ypad = g_value_get_int (value);
853 overlay->deltax = g_value_get_int (value);
856 overlay->deltay = g_value_get_int (value);
859 overlay->xpos = g_value_get_double (value);
862 overlay->ypos = g_value_get_double (value);
865 const gchar *s = g_value_get_string (value);
867 if (s && g_ascii_strcasecmp (s, "left") == 0)
868 overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT;
869 else if (s && g_ascii_strcasecmp (s, "center") == 0)
870 overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
871 else if (s && g_ascii_strcasecmp (s, "right") == 0)
872 overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
874 g_warning ("Invalid value '%s' for textoverlay property 'halign'",
879 const gchar *s = g_value_get_string (value);
881 if (s && g_ascii_strcasecmp (s, "baseline") == 0)
882 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
883 else if (s && g_ascii_strcasecmp (s, "bottom") == 0)
884 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM;
885 else if (s && g_ascii_strcasecmp (s, "top") == 0)
886 overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP;
888 g_warning ("Invalid value '%s' for textoverlay property 'valign'",
892 case PROP_VALIGNMENT:
893 overlay->valign = g_value_get_enum (value);
895 case PROP_HALIGNMENT:
896 overlay->halign = g_value_get_enum (value);
899 overlay->wrap_mode = g_value_get_enum (value);
900 g_mutex_lock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
901 gst_text_overlay_update_wrap_mode (overlay);
902 g_mutex_unlock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
906 PangoFontDescription *desc;
907 const gchar *fontdesc_str;
909 fontdesc_str = g_value_get_string (value);
910 g_mutex_lock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
911 desc = pango_font_description_from_string (fontdesc_str);
913 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
914 pango_layout_set_font_description (overlay->layout, desc);
915 gst_text_overlay_adjust_values_with_fontdesc (overlay, desc);
916 pango_font_description_free (desc);
918 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
921 g_mutex_unlock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
925 overlay->color = g_value_get_uint (value);
927 case PROP_OUTLINE_COLOR:
928 overlay->outline_color = g_value_get_uint (value);
931 overlay->silent = g_value_get_boolean (value);
933 case PROP_LINE_ALIGNMENT:
934 overlay->line_align = g_value_get_enum (value);
935 g_mutex_lock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
936 pango_layout_set_alignment (overlay->layout,
937 (PangoAlignment) overlay->line_align);
938 g_mutex_unlock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
941 overlay->wait_text = g_value_get_boolean (value);
943 case PROP_AUTO_ADJUST_SIZE:
944 overlay->auto_adjust_size = g_value_get_boolean (value);
945 overlay->need_render = TRUE;
947 case PROP_VERTICAL_RENDER:
948 overlay->use_vertical_render = g_value_get_boolean (value);
949 g_mutex_lock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
950 gst_text_overlay_update_render_mode (overlay);
951 g_mutex_unlock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
952 overlay->need_render = TRUE;
955 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
959 overlay->need_render = TRUE;
960 GST_OBJECT_UNLOCK (overlay);
964 gst_text_overlay_get_property (GObject * object, guint prop_id,
965 GValue * value, GParamSpec * pspec)
967 GstTextOverlay *overlay = GST_TEXT_OVERLAY (object);
969 GST_OBJECT_LOCK (overlay);
972 g_value_set_string (value, overlay->default_text);
975 g_value_set_boolean (value, overlay->want_shading);
978 g_value_set_boolean (value, overlay->want_shadow);
981 g_value_set_int (value, overlay->xpad);
984 g_value_set_int (value, overlay->ypad);
987 g_value_set_int (value, overlay->deltax);
990 g_value_set_int (value, overlay->deltay);
993 g_value_set_double (value, overlay->xpos);
996 g_value_set_double (value, overlay->ypos);
998 case PROP_VALIGNMENT:
999 g_value_set_enum (value, overlay->valign);
1001 case PROP_HALIGNMENT:
1002 g_value_set_enum (value, overlay->halign);
1004 case PROP_WRAP_MODE:
1005 g_value_set_enum (value, overlay->wrap_mode);
1008 g_value_set_boolean (value, overlay->silent);
1010 case PROP_LINE_ALIGNMENT:
1011 g_value_set_enum (value, overlay->line_align);
1013 case PROP_WAIT_TEXT:
1014 g_value_set_boolean (value, overlay->wait_text);
1016 case PROP_AUTO_ADJUST_SIZE:
1017 g_value_set_boolean (value, overlay->auto_adjust_size);
1019 case PROP_VERTICAL_RENDER:
1020 g_value_set_boolean (value, overlay->use_vertical_render);
1023 g_value_set_uint (value, overlay->color);
1025 case PROP_OUTLINE_COLOR:
1026 g_value_set_uint (value, overlay->outline_color);
1029 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1033 overlay->need_render = TRUE;
1034 GST_OBJECT_UNLOCK (overlay);
1038 gst_text_overlay_src_query (GstPad * pad, GstQuery * query)
1040 gboolean ret = FALSE;
1041 GstTextOverlay *overlay = NULL;
1043 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
1044 if (G_UNLIKELY (!overlay))
1047 ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1049 gst_object_unref (overlay);
1055 gst_text_overlay_src_event (GstPad * pad, GstEvent * event)
1057 gboolean ret = FALSE;
1058 GstTextOverlay *overlay = NULL;
1060 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
1061 if (G_UNLIKELY (!overlay)) {
1062 gst_event_unref (event);
1066 switch (GST_EVENT_TYPE (event)) {
1067 case GST_EVENT_SEEK:{
1070 /* We don't handle seek if we have not text pad */
1071 if (!overlay->text_linked) {
1072 GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1073 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1077 GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1079 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1081 /* Flush downstream, only for flushing seek */
1082 if (flags & GST_SEEK_FLAG_FLUSH)
1083 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1085 /* Mark ourself as flushing, unblock chains */
1086 GST_OBJECT_LOCK (overlay);
1087 overlay->video_flushing = TRUE;
1088 overlay->text_flushing = TRUE;
1089 gst_text_overlay_pop_text (overlay);
1090 GST_OBJECT_UNLOCK (overlay);
1092 /* Seek on each sink pad */
1093 gst_event_ref (event);
1094 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1096 ret = gst_pad_push_event (overlay->text_sinkpad, event);
1098 gst_event_unref (event);
1103 if (overlay->text_linked) {
1104 gst_event_ref (event);
1105 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1106 gst_pad_push_event (overlay->text_sinkpad, event);
1108 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1114 gst_object_unref (overlay);
1120 gst_text_overlay_getcaps (GstPad * pad)
1122 GstTextOverlay *overlay;
1126 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
1127 if (G_UNLIKELY (!overlay))
1128 return gst_caps_copy (gst_pad_get_pad_template_caps (pad));
1130 if (pad == overlay->srcpad)
1131 otherpad = overlay->video_sinkpad;
1133 otherpad = overlay->srcpad;
1135 /* we can do what the peer can */
1136 caps = gst_pad_peer_get_caps (otherpad);
1139 const GstCaps *templ;
1141 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
1143 /* filtered against our padtemplate */
1144 templ = gst_pad_get_pad_template_caps (otherpad);
1145 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
1146 temp = gst_caps_intersect (caps, templ);
1147 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1148 gst_caps_unref (caps);
1149 /* this is what we can do */
1152 /* no peer, our padtemplate is enough then */
1153 caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
1156 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1158 gst_object_unref (overlay);
1164 gst_text_overlay_adjust_values_with_fontdesc (GstTextOverlay * overlay,
1165 PangoFontDescription * desc)
1167 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1168 overlay->shadow_offset = (double) (font_size) / 13.0;
1169 overlay->outline_offset = (double) (font_size) / 15.0;
1170 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1171 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1174 #define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \
1175 *b = (a > 0) ? MIN ((*b * 255 + a / 2) / a, 255) : 0; \
1176 *g = (a > 0) ? MIN ((*g * 255 + a / 2) / a, 255) : 0; \
1177 *r = (a > 0) ? MIN ((*r * 255 + a / 2) / a, 255) : 0; \
1181 gst_text_overlay_get_pos (GstTextOverlay * overlay, gint * xpos, gint * ypos)
1184 GstTextOverlayVAlign valign;
1185 GstTextOverlayHAlign halign;
1187 width = overlay->image_width;
1188 height = overlay->image_height;
1190 if (overlay->use_vertical_render)
1191 halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
1193 halign = overlay->halign;
1196 case GST_TEXT_OVERLAY_HALIGN_LEFT:
1197 *xpos = overlay->xpad;
1199 case GST_TEXT_OVERLAY_HALIGN_CENTER:
1200 *xpos = (overlay->width - width) / 2;
1202 case GST_TEXT_OVERLAY_HALIGN_RIGHT:
1203 *xpos = overlay->width - width - overlay->xpad;
1205 case GST_TEXT_OVERLAY_HALIGN_POS:
1206 *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1207 *xpos = CLAMP (*xpos, 0, overlay->width - width);
1214 *xpos += overlay->deltax;
1216 if (overlay->use_vertical_render)
1217 valign = GST_TEXT_OVERLAY_VALIGN_TOP;
1219 valign = overlay->valign;
1222 case GST_TEXT_OVERLAY_VALIGN_BOTTOM:
1223 *ypos = overlay->height - height - overlay->ypad;
1225 case GST_TEXT_OVERLAY_VALIGN_BASELINE:
1226 *ypos = overlay->height - (height + overlay->ypad);
1228 case GST_TEXT_OVERLAY_VALIGN_TOP:
1229 *ypos = overlay->ypad;
1231 case GST_TEXT_OVERLAY_VALIGN_POS:
1232 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1233 *ypos = CLAMP (*ypos, 0, overlay->height - height);
1235 case GST_TEXT_OVERLAY_VALIGN_CENTER:
1236 *ypos = (overlay->height - height) / 2;
1239 *ypos = overlay->ypad;
1242 *ypos += overlay->deltay;
1246 gst_text_overlay_unpremultiply (GstTextOverlay * overlay)
1249 guint8 *pimage, *text_image = GST_BUFFER_DATA (overlay->text_image);
1251 for (i = 0; i < overlay->image_height; i++) {
1252 pimage = text_image + 4 * (i * overlay->image_width);
1253 for (j = 0; j < overlay->image_width; j++) {
1254 CAIRO_UNPREMULTIPLY (pimage[CAIRO_ARGB_A], &pimage[CAIRO_ARGB_R],
1255 &pimage[CAIRO_ARGB_G], &pimage[CAIRO_ARGB_B]);
1263 gst_text_overlay_set_composition (GstTextOverlay * overlay)
1266 GstVideoOverlayRectangle *rectangle;
1268 gst_text_overlay_get_pos (overlay, &xpos, &ypos);
1270 if (overlay->text_image) {
1271 rectangle = gst_video_overlay_rectangle_new_argb (overlay->text_image,
1272 overlay->image_width, overlay->image_height, 4 * overlay->image_width,
1273 xpos, ypos, overlay->image_width, overlay->image_height,
1274 GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE);
1276 if (overlay->composition)
1277 gst_video_overlay_composition_unref (overlay->composition);
1278 overlay->composition = gst_video_overlay_composition_new (rectangle);
1279 gst_video_overlay_rectangle_unref (rectangle);
1281 } else if (overlay->composition) {
1282 gst_video_overlay_composition_unref (overlay->composition);
1283 overlay->composition = NULL;
1288 gst_text_overlay_render_pangocairo (GstTextOverlay * overlay,
1289 const gchar * string, gint textlen)
1292 cairo_surface_t *surface;
1293 PangoRectangle ink_rect, logical_rect;
1294 cairo_matrix_t cairo_matrix;
1296 double scalef = 1.0;
1300 g_mutex_lock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1302 if (overlay->auto_adjust_size) {
1303 /* 640 pixel is default */
1304 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1306 pango_layout_set_width (overlay->layout, -1);
1307 /* set text on pango layout */
1308 pango_layout_set_markup (overlay->layout, string, textlen);
1310 /* get subtitle image size */
1311 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1313 width = (logical_rect.width + overlay->shadow_offset) * scalef;
1315 if (width + overlay->deltax >
1316 (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1318 * subtitle image width is larger then overlay width
1319 * so rearrange overlay wrap mode.
1321 gst_text_overlay_update_wrap_mode (overlay);
1322 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1323 width = overlay->width;
1327 (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1328 if (height > overlay->height) {
1329 height = overlay->height;
1331 if (overlay->use_vertical_render) {
1332 PangoRectangle rect;
1333 PangoContext *context;
1334 PangoMatrix matrix = PANGO_MATRIX_INIT;
1337 context = pango_layout_get_context (overlay->layout);
1339 pango_matrix_rotate (&matrix, -90);
1341 rect.x = rect.y = 0;
1343 rect.height = height;
1344 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1345 matrix.x0 = -rect.x;
1346 matrix.y0 = -rect.y;
1348 pango_context_set_matrix (context, &matrix);
1350 cairo_matrix.xx = matrix.xx;
1351 cairo_matrix.yx = matrix.yx;
1352 cairo_matrix.xy = matrix.xy;
1353 cairo_matrix.yy = matrix.yy;
1354 cairo_matrix.x0 = matrix.x0;
1355 cairo_matrix.y0 = matrix.y0;
1356 cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1362 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1365 /* reallocate overlay buffer */
1366 buffer = gst_buffer_new_and_alloc (4 * width * height);
1367 gst_buffer_replace (&overlay->text_image, buffer);
1368 text_image = GST_BUFFER_DATA (buffer);
1369 gst_buffer_unref (buffer);
1371 surface = cairo_image_surface_create_for_data (text_image,
1372 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1373 cr = cairo_create (surface);
1376 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1379 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1381 if (overlay->want_shading)
1382 cairo_paint_with_alpha (cr, overlay->shading_value);
1384 /* apply transformations */
1385 cairo_set_matrix (cr, &cairo_matrix);
1387 /* FIXME: We use show_layout everywhere except for the surface
1388 * because it's really faster and internally does all kinds of
1389 * caching. Unfortunately we have to paint to a cairo path for
1390 * the outline and this is slow. Once Pango supports user fonts
1391 * we should use them, see
1392 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1394 * Idea would the be, to create a cairo user font that
1395 * does shadow, outline, text painting in the
1396 * render_glyph function.
1399 /* draw shadow text */
1400 if (overlay->want_shadow) {
1402 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1403 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1404 pango_cairo_show_layout (cr, overlay->layout);
1408 a = (overlay->outline_color >> 24) & 0xff;
1409 r = (overlay->outline_color >> 16) & 0xff;
1410 g = (overlay->outline_color >> 8) & 0xff;
1411 b = (overlay->outline_color >> 0) & 0xff;
1413 /* draw outline text */
1415 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1416 cairo_set_line_width (cr, overlay->outline_offset);
1417 pango_cairo_layout_path (cr, overlay->layout);
1421 a = (overlay->color >> 24) & 0xff;
1422 r = (overlay->color >> 16) & 0xff;
1423 g = (overlay->color >> 8) & 0xff;
1424 b = (overlay->color >> 0) & 0xff;
1428 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1429 pango_cairo_show_layout (cr, overlay->layout);
1433 cairo_surface_destroy (surface);
1434 overlay->image_width = width;
1435 overlay->image_height = height;
1436 overlay->baseline_y = ink_rect.y;
1438 g_mutex_unlock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1440 /* As the GstVideoOverlayComposition supports only unpremultiply ARGB,
1441 * we need to unpermultiply it */
1442 gst_text_overlay_unpremultiply (overlay);
1443 gst_text_overlay_set_composition (overlay);
1450 gst_text_overlay_shade_planar_Y (GstTextOverlay * overlay, guchar * dest,
1451 gint x0, gint x1, gint y0, gint y1)
1453 gint i, j, dest_stride;
1455 dest_stride = gst_video_format_get_row_stride (overlay->format, 0,
1458 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1459 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1461 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1462 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1464 for (i = y0; i < y1; ++i) {
1465 for (j = x0; j < x1; ++j) {
1466 gint y = dest[(i * dest_stride) + j] + overlay->shading_value;
1468 dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1474 gst_text_overlay_shade_packed_Y (GstTextOverlay * overlay, guchar * dest,
1475 gint x0, gint x1, gint y0, gint y1)
1478 guint dest_stride, pixel_stride, component_offset;
1480 dest_stride = gst_video_format_get_row_stride (overlay->format, 0,
1482 pixel_stride = gst_video_format_get_pixel_stride (overlay->format, 0);
1484 gst_video_format_get_component_offset (overlay->format, 0, overlay->width,
1487 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1488 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1490 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1491 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1494 x0 = gst_video_format_get_component_width (overlay->format, 0, x0);
1496 x1 = gst_video_format_get_component_width (overlay->format, 0, x1);
1499 y0 = gst_video_format_get_component_height (overlay->format, 0, y0);
1501 y1 = gst_video_format_get_component_height (overlay->format, 0, y1);
1503 for (i = y0; i < y1; i++) {
1504 for (j = x0; j < x1; j++) {
1508 y_pos = (i * dest_stride) + j * pixel_stride + component_offset;
1509 y = dest[y_pos] + overlay->shading_value;
1511 dest[y_pos] = CLAMP (y, 0, 255);
1516 #define gst_text_overlay_shade_BGRx gst_text_overlay_shade_xRGB
1517 #define gst_text_overlay_shade_RGBx gst_text_overlay_shade_xRGB
1518 #define gst_text_overlay_shade_xBGR gst_text_overlay_shade_xRGB
1520 gst_text_overlay_shade_xRGB (GstTextOverlay * overlay, guchar * dest,
1521 gint x0, gint x1, gint y0, gint y1)
1525 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1526 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1528 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1529 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1531 for (i = y0; i < y1; i++) {
1532 for (j = x0; j < x1; j++) {
1535 y_pos = (i * 4 * overlay->width) + j * 4;
1536 for (k = 0; k < 4; k++) {
1537 y = dest[y_pos + k] + overlay->shading_value;
1538 dest[y_pos + k] = CLAMP (y, 0, 255);
1544 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
1545 static inline void \
1546 gst_text_overlay_shade_##name (GstTextOverlay * overlay, guchar * dest, \
1547 gint x0, gint x1, gint y0, gint y1) \
1551 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1552 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1554 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1555 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1557 for (i = y0; i < y1; i++) {\
1558 for (j = x0; j < x1; j++) {\
1560 y_pos = (i * 4 * overlay->width) + j * 4;\
1561 for (k = OFFSET; k < 3+OFFSET; k++) {\
1562 y = dest[y_pos + k] + overlay->shading_value;\
1563 dest[y_pos + k] = CLAMP (y, 0, 255);\
1568 ARGB_SHADE_FUNCTION (ARGB, 1);
1569 ARGB_SHADE_FUNCTION (ABGR, 1);
1570 ARGB_SHADE_FUNCTION (RGBA, 0);
1571 ARGB_SHADE_FUNCTION (BGRA, 0);
1575 gst_text_overlay_render_text (GstTextOverlay * overlay,
1576 const gchar * text, gint textlen)
1580 if (!overlay->need_render) {
1581 GST_DEBUG ("Using previously rendered text.");
1585 /* -1 is the whole string */
1586 if (text != NULL && textlen < 0) {
1587 textlen = strlen (text);
1591 string = g_strndup (text, textlen);
1592 } else { /* empty string */
1593 string = g_strdup (" ");
1595 g_strdelimit (string, "\r\t", ' ');
1596 textlen = strlen (string);
1598 /* FIXME: should we check for UTF-8 here? */
1600 GST_DEBUG ("Rendering '%s'", string);
1601 gst_text_overlay_render_pangocairo (overlay, string, textlen);
1605 overlay->need_render = FALSE;
1608 static GstFlowReturn
1609 gst_text_overlay_push_frame (GstTextOverlay * overlay, GstBuffer * video_frame)
1613 video_frame = gst_buffer_make_writable (video_frame);
1615 gst_text_overlay_get_pos (overlay, &xpos, &ypos);
1616 /* shaded background box */
1617 if (overlay->want_shading) {
1618 switch (overlay->format) {
1619 case GST_VIDEO_FORMAT_I420:
1620 case GST_VIDEO_FORMAT_YV12:
1621 case GST_VIDEO_FORMAT_NV12:
1622 case GST_VIDEO_FORMAT_NV21:
1623 gst_text_overlay_shade_planar_Y (overlay,
1624 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1625 ypos, ypos + overlay->image_height);
1627 case GST_VIDEO_FORMAT_AYUV:
1628 case GST_VIDEO_FORMAT_UYVY:
1629 gst_text_overlay_shade_packed_Y (overlay,
1630 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1631 ypos, ypos + overlay->image_height);
1633 case GST_VIDEO_FORMAT_xRGB:
1634 gst_text_overlay_shade_xRGB (overlay,
1635 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1636 ypos, ypos + overlay->image_height);
1638 case GST_VIDEO_FORMAT_xBGR:
1639 gst_text_overlay_shade_xBGR (overlay,
1640 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1641 ypos, ypos + overlay->image_height);
1643 case GST_VIDEO_FORMAT_BGRx:
1644 gst_text_overlay_shade_BGRx (overlay,
1645 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1646 ypos, ypos + overlay->image_height);
1648 case GST_VIDEO_FORMAT_RGBx:
1649 gst_text_overlay_shade_RGBx (overlay,
1650 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1651 ypos, ypos + overlay->image_height);
1653 case GST_VIDEO_FORMAT_ARGB:
1654 gst_text_overlay_shade_ARGB (overlay,
1655 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1656 ypos, ypos + overlay->image_height);
1658 case GST_VIDEO_FORMAT_ABGR:
1659 gst_text_overlay_shade_ABGR (overlay,
1660 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1661 ypos, ypos + overlay->image_height);
1663 case GST_VIDEO_FORMAT_RGBA:
1664 gst_text_overlay_shade_RGBA (overlay,
1665 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1666 ypos, ypos + overlay->image_height);
1668 case GST_VIDEO_FORMAT_BGRA:
1669 gst_text_overlay_shade_BGRA (overlay,
1670 GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->image_width,
1671 ypos, ypos + overlay->image_height);
1674 g_assert_not_reached ();
1678 if (overlay->composition) {
1679 if (overlay->attach_compo_to_buffer) {
1680 GST_DEBUG_OBJECT (overlay, "Attaching text to the buffer");
1681 gst_video_buffer_set_overlay_composition (video_frame,
1682 overlay->composition);
1684 gst_video_overlay_composition_blend (overlay->composition, video_frame);
1688 return gst_pad_push (overlay->srcpad, video_frame);
1691 static GstPadLinkReturn
1692 gst_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1694 GstTextOverlay *overlay;
1696 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
1697 if (G_UNLIKELY (!overlay))
1698 return GST_PAD_LINK_REFUSED;
1700 GST_DEBUG_OBJECT (overlay, "Text pad linked");
1702 overlay->text_linked = TRUE;
1704 gst_object_unref (overlay);
1706 return GST_PAD_LINK_OK;
1710 gst_text_overlay_text_pad_unlink (GstPad * pad)
1712 GstTextOverlay *overlay;
1714 /* don't use gst_pad_get_parent() here, will deadlock */
1715 overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1717 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1719 overlay->text_linked = FALSE;
1721 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1725 gst_text_overlay_text_event (GstPad * pad, GstEvent * event)
1727 gboolean ret = FALSE;
1728 GstTextOverlay *overlay = NULL;
1730 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
1731 if (G_UNLIKELY (!overlay)) {
1732 gst_event_unref (event);
1736 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1738 switch (GST_EVENT_TYPE (event)) {
1739 case GST_EVENT_NEWSEGMENT:{
1742 gdouble rate, applied_rate;
1743 gint64 cur, stop, time;
1745 overlay->text_eos = FALSE;
1747 gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
1748 &fmt, &cur, &stop, &time);
1750 if (fmt == GST_FORMAT_TIME) {
1751 GST_OBJECT_LOCK (overlay);
1752 gst_segment_set_newsegment_full (&overlay->text_segment, update, rate,
1753 applied_rate, GST_FORMAT_TIME, cur, stop, time);
1754 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1755 &overlay->text_segment);
1756 GST_OBJECT_UNLOCK (overlay);
1758 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1759 ("received non-TIME newsegment event on text input"));
1762 gst_event_unref (event);
1765 /* wake up the video chain, it might be waiting for a text buffer or
1766 * a text segment update */
1767 GST_OBJECT_LOCK (overlay);
1768 GST_TEXT_OVERLAY_BROADCAST (overlay);
1769 GST_OBJECT_UNLOCK (overlay);
1772 case GST_EVENT_FLUSH_STOP:
1773 GST_OBJECT_LOCK (overlay);
1774 GST_INFO_OBJECT (overlay, "text flush stop");
1775 overlay->text_flushing = FALSE;
1776 overlay->text_eos = FALSE;
1777 gst_text_overlay_pop_text (overlay);
1778 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1779 GST_OBJECT_UNLOCK (overlay);
1780 gst_event_unref (event);
1783 case GST_EVENT_FLUSH_START:
1784 GST_OBJECT_LOCK (overlay);
1785 GST_INFO_OBJECT (overlay, "text flush start");
1786 overlay->text_flushing = TRUE;
1787 GST_TEXT_OVERLAY_BROADCAST (overlay);
1788 GST_OBJECT_UNLOCK (overlay);
1789 gst_event_unref (event);
1793 GST_OBJECT_LOCK (overlay);
1794 overlay->text_eos = TRUE;
1795 GST_INFO_OBJECT (overlay, "text EOS");
1796 /* wake up the video chain, it might be waiting for a text buffer or
1797 * a text segment update */
1798 GST_TEXT_OVERLAY_BROADCAST (overlay);
1799 GST_OBJECT_UNLOCK (overlay);
1800 gst_event_unref (event);
1804 ret = gst_pad_event_default (pad, event);
1808 gst_object_unref (overlay);
1814 gst_text_overlay_video_event (GstPad * pad, GstEvent * event)
1816 gboolean ret = FALSE;
1817 GstTextOverlay *overlay = NULL;
1819 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
1820 if (G_UNLIKELY (!overlay)) {
1821 gst_event_unref (event);
1825 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1827 switch (GST_EVENT_TYPE (event)) {
1828 case GST_EVENT_NEWSEGMENT:
1832 gint64 start, stop, time;
1835 GST_DEBUG_OBJECT (overlay, "received new segment");
1837 gst_event_parse_new_segment (event, &update, &rate, &format, &start,
1840 if (format == GST_FORMAT_TIME) {
1841 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1844 gst_segment_set_newsegment (&overlay->segment, update, rate, format,
1847 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1848 ("received non-TIME newsegment event on video input"));
1851 ret = gst_pad_event_default (pad, event);
1855 GST_OBJECT_LOCK (overlay);
1856 GST_INFO_OBJECT (overlay, "video EOS");
1857 overlay->video_eos = TRUE;
1858 GST_OBJECT_UNLOCK (overlay);
1859 ret = gst_pad_event_default (pad, event);
1861 case GST_EVENT_FLUSH_START:
1862 GST_OBJECT_LOCK (overlay);
1863 GST_INFO_OBJECT (overlay, "video flush start");
1864 overlay->video_flushing = TRUE;
1865 GST_TEXT_OVERLAY_BROADCAST (overlay);
1866 GST_OBJECT_UNLOCK (overlay);
1867 ret = gst_pad_event_default (pad, event);
1869 case GST_EVENT_FLUSH_STOP:
1870 GST_OBJECT_LOCK (overlay);
1871 GST_INFO_OBJECT (overlay, "video flush stop");
1872 overlay->video_flushing = FALSE;
1873 overlay->video_eos = FALSE;
1874 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1875 GST_OBJECT_UNLOCK (overlay);
1876 ret = gst_pad_event_default (pad, event);
1879 ret = gst_pad_event_default (pad, event);
1883 gst_object_unref (overlay);
1888 static GstFlowReturn
1889 gst_text_overlay_video_bufferalloc (GstPad * pad, guint64 offset, guint size,
1890 GstCaps * caps, GstBuffer ** buffer)
1892 GstTextOverlay *overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
1893 GstFlowReturn ret = GST_FLOW_WRONG_STATE;
1896 if (G_UNLIKELY (!overlay))
1897 return GST_FLOW_WRONG_STATE;
1899 GST_OBJECT_LOCK (overlay);
1900 allocpad = overlay->srcpad ? gst_object_ref (overlay->srcpad) : NULL;
1901 GST_OBJECT_UNLOCK (overlay);
1904 ret = gst_pad_alloc_buffer (allocpad, offset, size, caps, buffer);
1905 gst_object_unref (allocpad);
1908 gst_object_unref (overlay);
1912 /* Called with lock held */
1914 gst_text_overlay_pop_text (GstTextOverlay * overlay)
1916 g_return_if_fail (GST_IS_TEXT_OVERLAY (overlay));
1918 if (overlay->text_buffer) {
1919 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1920 overlay->text_buffer);
1921 gst_buffer_unref (overlay->text_buffer);
1922 overlay->text_buffer = NULL;
1925 /* Let the text task know we used that buffer */
1926 GST_TEXT_OVERLAY_BROADCAST (overlay);
1929 /* We receive text buffers here. If they are out of segment we just ignore them.
1930 If the buffer is in our segment we keep it internally except if another one
1931 is already waiting here, in that case we wait that it gets kicked out */
1932 static GstFlowReturn
1933 gst_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer)
1935 GstFlowReturn ret = GST_FLOW_OK;
1936 GstTextOverlay *overlay = NULL;
1937 gboolean in_seg = FALSE;
1938 gint64 clip_start = 0, clip_stop = 0;
1940 overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1942 GST_OBJECT_LOCK (overlay);
1944 if (overlay->text_flushing) {
1945 GST_OBJECT_UNLOCK (overlay);
1946 ret = GST_FLOW_WRONG_STATE;
1947 GST_LOG_OBJECT (overlay, "text flushing");
1951 if (overlay->text_eos) {
1952 GST_OBJECT_UNLOCK (overlay);
1953 ret = GST_FLOW_UNEXPECTED;
1954 GST_LOG_OBJECT (overlay, "text EOS");
1958 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1959 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1960 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1961 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1962 GST_BUFFER_DURATION (buffer)));
1964 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1967 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1968 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1970 stop = GST_CLOCK_TIME_NONE;
1972 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
1973 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1979 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1980 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1981 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1982 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1984 if (overlay->text_buffer
1985 && (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer)
1986 || !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer))) {
1987 gst_text_overlay_pop_text (overlay);
1989 /* Wait for the previous buffer to go away */
1990 while (overlay->text_buffer != NULL) {
1991 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
1992 GST_DEBUG_PAD_NAME (pad));
1993 GST_TEXT_OVERLAY_WAIT (overlay);
1994 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
1995 if (overlay->text_flushing) {
1996 GST_OBJECT_UNLOCK (overlay);
1997 ret = GST_FLOW_WRONG_STATE;
2003 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2004 gst_segment_set_last_stop (&overlay->text_segment, GST_FORMAT_TIME,
2007 overlay->text_buffer = gst_buffer_ref (buffer);
2008 /* That's a new text buffer we need to render */
2009 overlay->need_render = TRUE;
2011 /* in case the video chain is waiting for a text buffer, wake it up */
2012 GST_TEXT_OVERLAY_BROADCAST (overlay);
2015 GST_OBJECT_UNLOCK (overlay);
2019 gst_buffer_unref (buffer);
2023 static GstFlowReturn
2024 gst_text_overlay_video_chain (GstPad * pad, GstBuffer * buffer)
2026 GstTextOverlayClass *klass;
2027 GstTextOverlay *overlay;
2028 GstFlowReturn ret = GST_FLOW_OK;
2029 gboolean in_seg = FALSE;
2030 gint64 start, stop, clip_start = 0, clip_stop = 0;
2033 overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad));
2034 klass = GST_TEXT_OVERLAY_GET_CLASS (overlay);
2036 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2037 goto missing_timestamp;
2039 /* ignore buffers that are outside of the current segment */
2040 start = GST_BUFFER_TIMESTAMP (buffer);
2042 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2043 stop = GST_CLOCK_TIME_NONE;
2045 stop = start + GST_BUFFER_DURATION (buffer);
2048 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2049 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2050 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2052 /* segment_clip() will adjust start unconditionally to segment_start if
2053 * no stop time is provided, so handle this ourselves */
2054 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2055 goto out_of_segment;
2057 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2058 &clip_start, &clip_stop);
2061 goto out_of_segment;
2063 /* if the buffer is only partially in the segment, fix up stamps */
2064 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2065 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2066 buffer = gst_buffer_make_metadata_writable (buffer);
2067 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2069 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2072 /* now, after we've done the clipping, fix up end time if there's no
2073 * duration (we only use those estimated values internally though, we
2074 * don't want to set bogus values on the buffer itself) */
2077 gint fps_num, fps_denom;
2079 s = gst_caps_get_structure (GST_PAD_CAPS (pad), 0);
2080 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2081 fps_num && fps_denom) {
2082 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2083 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2085 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2086 stop = start + 1; /* we need to assume some interval */
2090 gst_object_sync_values (G_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2094 GST_OBJECT_LOCK (overlay);
2096 if (overlay->video_flushing)
2099 if (overlay->video_eos)
2102 if (overlay->silent && !overlay->text_linked) {
2103 GST_OBJECT_UNLOCK (overlay);
2104 ret = gst_pad_push (overlay->srcpad, buffer);
2106 /* Update last_stop */
2107 gst_segment_set_last_stop (&overlay->segment, GST_FORMAT_TIME, clip_start);
2112 /* Text pad not linked, rendering internal text */
2113 if (!overlay->text_linked) {
2114 if (klass->get_text) {
2115 text = klass->get_text (overlay, buffer);
2117 text = g_strdup (overlay->default_text);
2120 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2121 "text: '%s'", GST_STR_NULL (text));
2123 GST_OBJECT_UNLOCK (overlay);
2125 if (text != NULL && *text != '\0') {
2126 /* Render and push */
2127 gst_text_overlay_render_text (overlay, text, -1);
2128 ret = gst_text_overlay_push_frame (overlay, buffer);
2130 /* Invalid or empty string */
2131 ret = gst_pad_push (overlay->srcpad, buffer);
2134 /* Text pad linked, check if we have a text buffer queued */
2135 if (overlay->text_buffer) {
2136 gboolean pop_text = FALSE, valid_text_time = TRUE;
2137 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2138 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2139 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2140 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2141 GstClockTime vid_running_time, vid_running_time_end;
2143 /* if the text buffer isn't stamped right, pop it off the
2144 * queue and display it for the current video frame only */
2145 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2146 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2147 GST_WARNING_OBJECT (overlay,
2148 "Got text buffer with invalid timestamp or duration");
2149 valid_text_time = FALSE;
2151 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2152 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2156 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2158 vid_running_time_end =
2159 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2162 /* If timestamp and duration are valid */
2163 if (valid_text_time) {
2165 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2167 text_running_time_end =
2168 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2172 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2173 GST_TIME_ARGS (text_running_time),
2174 GST_TIME_ARGS (text_running_time_end));
2175 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2176 GST_TIME_ARGS (vid_running_time),
2177 GST_TIME_ARGS (vid_running_time_end));
2179 /* Text too old or in the future */
2180 if (valid_text_time && text_running_time_end <= vid_running_time) {
2181 /* text buffer too old, get rid of it and do nothing */
2182 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2184 gst_text_overlay_pop_text (overlay);
2185 GST_OBJECT_UNLOCK (overlay);
2186 goto wait_for_text_buf;
2187 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2188 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2189 GST_OBJECT_UNLOCK (overlay);
2190 /* Push the video frame */
2191 ret = gst_pad_push (overlay->srcpad, buffer);
2192 } else if (overlay->silent) {
2193 GST_LOG_OBJECT (overlay, "silent enabled, pushing video buf");
2194 GST_OBJECT_UNLOCK (overlay);
2195 /* Push the video frame */
2196 ret = gst_pad_push (overlay->srcpad, buffer);
2201 in_text = (gchar *) GST_BUFFER_DATA (overlay->text_buffer);
2202 in_size = GST_BUFFER_SIZE (overlay->text_buffer);
2204 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2205 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2206 * here on purpose, this is something that needs fixing upstream */
2207 if (!g_utf8_validate (in_text, in_size, NULL)) {
2208 const gchar *end = NULL;
2210 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2211 in_text = g_strndup (in_text, in_size);
2212 while (!g_utf8_validate (in_text, in_size, &end) && end)
2213 *((gchar *) end) = '*';
2216 /* Get the string */
2217 if (overlay->have_pango_markup) {
2218 text = g_strndup (in_text, in_size);
2220 text = g_markup_escape_text (in_text, in_size);
2223 if (text != NULL && *text != '\0') {
2224 gint text_len = strlen (text);
2226 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2227 text[text_len - 1] == '\r')) {
2230 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2231 gst_text_overlay_render_text (overlay, text, text_len);
2233 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2234 gst_text_overlay_render_text (overlay, " ", 1);
2237 if (in_text != (gchar *) GST_BUFFER_DATA (overlay->text_buffer))
2240 GST_OBJECT_UNLOCK (overlay);
2241 ret = gst_text_overlay_push_frame (overlay, buffer);
2243 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2244 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2249 GST_OBJECT_LOCK (overlay);
2250 gst_text_overlay_pop_text (overlay);
2251 GST_OBJECT_UNLOCK (overlay);
2254 gboolean wait_for_text_buf = TRUE;
2256 if (overlay->text_eos)
2257 wait_for_text_buf = FALSE;
2259 if (!overlay->wait_text)
2260 wait_for_text_buf = FALSE;
2262 /* Text pad linked, but no text buffer available - what now? */
2263 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2264 GstClockTime text_start_running_time, text_last_stop_running_time;
2265 GstClockTime vid_running_time;
2268 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2269 GST_BUFFER_TIMESTAMP (buffer));
2270 text_start_running_time =
2271 gst_segment_to_running_time (&overlay->text_segment,
2272 GST_FORMAT_TIME, overlay->text_segment.start);
2273 text_last_stop_running_time =
2274 gst_segment_to_running_time (&overlay->text_segment,
2275 GST_FORMAT_TIME, overlay->text_segment.last_stop);
2277 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2278 vid_running_time < text_start_running_time) ||
2279 (GST_CLOCK_TIME_IS_VALID (text_last_stop_running_time) &&
2280 vid_running_time < text_last_stop_running_time)) {
2281 wait_for_text_buf = FALSE;
2285 if (wait_for_text_buf) {
2286 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2287 GST_TEXT_OVERLAY_WAIT (overlay);
2288 GST_DEBUG_OBJECT (overlay, "resuming");
2289 GST_OBJECT_UNLOCK (overlay);
2290 goto wait_for_text_buf;
2292 GST_OBJECT_UNLOCK (overlay);
2293 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2294 ret = gst_pad_push (overlay->srcpad, buffer);
2301 /* Update last_stop */
2302 gst_segment_set_last_stop (&overlay->segment, GST_FORMAT_TIME, clip_start);
2308 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2309 gst_buffer_unref (buffer);
2315 GST_OBJECT_UNLOCK (overlay);
2316 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2317 gst_buffer_unref (buffer);
2318 return GST_FLOW_WRONG_STATE;
2322 GST_OBJECT_UNLOCK (overlay);
2323 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2324 gst_buffer_unref (buffer);
2325 return GST_FLOW_UNEXPECTED;
2329 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2330 gst_buffer_unref (buffer);
2335 static GstStateChangeReturn
2336 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
2338 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2339 GstTextOverlay *overlay = GST_TEXT_OVERLAY (element);
2341 switch (transition) {
2342 case GST_STATE_CHANGE_PAUSED_TO_READY:
2343 GST_OBJECT_LOCK (overlay);
2344 overlay->text_flushing = TRUE;
2345 overlay->video_flushing = TRUE;
2346 /* pop_text will broadcast on the GCond and thus also make the video
2347 * chain exit if it's waiting for a text buffer */
2348 gst_text_overlay_pop_text (overlay);
2349 GST_OBJECT_UNLOCK (overlay);
2355 ret = parent_class->change_state (element, transition);
2356 if (ret == GST_STATE_CHANGE_FAILURE)
2359 switch (transition) {
2360 case GST_STATE_CHANGE_READY_TO_PAUSED:
2361 GST_OBJECT_LOCK (overlay);
2362 overlay->text_flushing = FALSE;
2363 overlay->video_flushing = FALSE;
2364 overlay->video_eos = FALSE;
2365 overlay->text_eos = FALSE;
2366 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2367 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2368 GST_OBJECT_UNLOCK (overlay);
2378 plugin_init (GstPlugin * plugin)
2380 gst_controller_init (NULL, NULL);
2382 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2383 GST_TYPE_TEXT_OVERLAY) ||
2384 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2385 GST_TYPE_TIME_OVERLAY) ||
2386 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2387 GST_TYPE_CLOCK_OVERLAY) ||
2388 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2389 GST_TYPE_TEXT_RENDER)) {
2393 /*texttestsrc_plugin_init(module, plugin); */
2395 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2400 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2401 "pango", "Pango-based text rendering and overlay", plugin_init,
2402 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)