2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) <2003> David Schleef <ds@schleef.org>
4 * Copyright (C) <2006> Julien Moutte <julien@moutte.net>
5 * Copyright (C) <2006> Zeeshan Ali <zeeshan.ali@nokia.com>
6 * Copyright (C) <2006-2008> Tim-Philipp Müller <tim centricular net>
7 * Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
19 * You should have received a copy of the GNU Library General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
29 #include <gst/video/video.h>
30 #include <gst/video/gstvideometa.h>
32 #include "gstbasetextoverlay.h"
33 #include "gsttextoverlay.h"
34 #include "gsttimeoverlay.h"
35 #include "gstclockoverlay.h"
36 #include "gsttextrender.h"
41 * - use proper strides and offset for I420
42 * - if text is wider than the video picture, it does not get
43 * clipped properly during blitting (if wrapping is disabled)
46 GST_DEBUG_CATEGORY (pango_debug);
47 #define GST_CAT_DEFAULT pango_debug
49 #define DEFAULT_PROP_TEXT ""
50 #define DEFAULT_PROP_SHADING FALSE
51 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
52 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
53 #define DEFAULT_PROP_XPAD 25
54 #define DEFAULT_PROP_YPAD 25
55 #define DEFAULT_PROP_DELTAX 0
56 #define DEFAULT_PROP_DELTAY 0
57 #define DEFAULT_PROP_XPOS 0.5
58 #define DEFAULT_PROP_YPOS 0.5
59 #define DEFAULT_PROP_WRAP_MODE GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
60 #define DEFAULT_PROP_FONT_DESC ""
61 #define DEFAULT_PROP_SILENT FALSE
62 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
63 #define DEFAULT_PROP_WAIT_TEXT TRUE
64 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
65 #define DEFAULT_PROP_VERTICAL_RENDER FALSE
66 #define DEFAULT_PROP_DRAW_SHADOW TRUE
67 #define DEFAULT_PROP_DRAW_OUTLINE TRUE
68 #define DEFAULT_PROP_COLOR 0xffffffff
69 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
70 #define DEFAULT_PROP_SHADING_VALUE 80
71 #define DEFAULT_PROP_TEXT_X 0
72 #define DEFAULT_PROP_TEXT_Y 0
73 #define DEFAULT_PROP_TEXT_WIDTH 1
74 #define DEFAULT_PROP_TEXT_HEIGHT 1
76 #define MINIMUM_OUTLINE_OFFSET 1.0
77 #define DEFAULT_SCALE_BASIS 640
98 PROP_AUTO_ADJUST_SIZE,
111 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
113 #define BASE_TEXT_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
115 #define BASE_TEXT_OVERLAY_ALL_CAPS BASE_TEXT_OVERLAY_CAPS ";" \
116 GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
118 static GstStaticCaps sw_template_caps =
119 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
121 static GstStaticPadTemplate src_template_factory =
122 GST_STATIC_PAD_TEMPLATE ("src",
125 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
128 static GstStaticPadTemplate video_sink_template_factory =
129 GST_STATIC_PAD_TEMPLATE ("video_sink",
132 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
135 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
137 gst_base_text_overlay_valign_get_type (void)
139 static GType base_text_overlay_valign_type = 0;
140 static const GEnumValue base_text_overlay_valign[] = {
141 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
142 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
143 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
144 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
145 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
149 if (!base_text_overlay_valign_type) {
150 base_text_overlay_valign_type =
151 g_enum_register_static ("GstBaseTextOverlayVAlign",
152 base_text_overlay_valign);
154 return base_text_overlay_valign_type;
157 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
159 gst_base_text_overlay_halign_get_type (void)
161 static GType base_text_overlay_halign_type = 0;
162 static const GEnumValue base_text_overlay_halign[] = {
163 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
164 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
165 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
166 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
170 if (!base_text_overlay_halign_type) {
171 base_text_overlay_halign_type =
172 g_enum_register_static ("GstBaseTextOverlayHAlign",
173 base_text_overlay_halign);
175 return base_text_overlay_halign_type;
179 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
181 gst_base_text_overlay_wrap_mode_get_type (void)
183 static GType base_text_overlay_wrap_mode_type = 0;
184 static const GEnumValue base_text_overlay_wrap_mode[] = {
185 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
186 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
187 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
188 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
192 if (!base_text_overlay_wrap_mode_type) {
193 base_text_overlay_wrap_mode_type =
194 g_enum_register_static ("GstBaseTextOverlayWrapMode",
195 base_text_overlay_wrap_mode);
197 return base_text_overlay_wrap_mode_type;
200 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
202 gst_base_text_overlay_line_align_get_type (void)
204 static GType base_text_overlay_line_align_type = 0;
205 static const GEnumValue base_text_overlay_line_align[] = {
206 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
207 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
208 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
212 if (!base_text_overlay_line_align_type) {
213 base_text_overlay_line_align_type =
214 g_enum_register_static ("GstBaseTextOverlayLineAlign",
215 base_text_overlay_line_align);
217 return base_text_overlay_line_align_type;
220 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
221 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
222 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
223 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
224 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
225 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
226 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
228 static GstElementClass *parent_class = NULL;
229 static void gst_base_text_overlay_base_init (gpointer g_class);
230 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
231 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
232 GstBaseTextOverlayClass * klass);
234 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
235 element, GstStateChange transition);
237 static GstCaps *gst_base_text_overlay_get_videosink_caps (GstPad * pad,
238 GstBaseTextOverlay * overlay, GstCaps * filter);
239 static GstCaps *gst_base_text_overlay_get_src_caps (GstPad * pad,
240 GstBaseTextOverlay * overlay, GstCaps * filter);
241 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
243 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
245 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
246 GstObject * parent, GstEvent * event);
247 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
248 GstObject * parent, GstQuery * query);
250 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
251 GstObject * parent, GstEvent * event);
252 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
253 GstObject * parent, GstQuery * query);
254 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
255 GstObject * parent, GstBuffer * buffer);
257 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
258 GstObject * parent, GstEvent * event);
259 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
260 GstObject * parent, GstBuffer * buffer);
261 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
262 GstObject * parent, GstPad * peer);
263 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
265 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
267 static void gst_base_text_overlay_finalize (GObject * object);
268 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
269 const GValue * value, GParamSpec * pspec);
270 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
271 GValue * value, GParamSpec * pspec);
274 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
275 PangoFontDescription * desc);
276 static gboolean gst_base_text_overlay_can_handle_caps (GstCaps * incaps);
279 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay);
282 gst_base_text_overlay_get_type (void)
284 static GType type = 0;
286 if (g_once_init_enter ((gsize *) & type)) {
287 static const GTypeInfo info = {
288 sizeof (GstBaseTextOverlayClass),
289 (GBaseInitFunc) gst_base_text_overlay_base_init,
291 (GClassInitFunc) gst_base_text_overlay_class_init,
294 sizeof (GstBaseTextOverlay),
296 (GInstanceInitFunc) gst_base_text_overlay_init,
299 g_once_init_leave ((gsize *) & type,
300 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
308 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
309 GstBuffer * video_frame)
311 return g_strdup (overlay->default_text);
315 gst_base_text_overlay_base_init (gpointer g_class)
317 GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
318 PangoFontMap *fontmap;
320 /* Only lock for the subclasses here, the base class
321 * doesn't have this mutex yet and it's not necessary
323 if (klass->pango_lock)
324 g_mutex_lock (klass->pango_lock);
325 fontmap = pango_cairo_font_map_get_default ();
326 klass->pango_context =
327 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
328 pango_context_set_base_gravity (klass->pango_context, PANGO_GRAVITY_SOUTH);
329 if (klass->pango_lock)
330 g_mutex_unlock (klass->pango_lock);
334 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
336 GObjectClass *gobject_class;
337 GstElementClass *gstelement_class;
339 gobject_class = (GObjectClass *) klass;
340 gstelement_class = (GstElementClass *) klass;
342 parent_class = g_type_class_peek_parent (klass);
344 gobject_class->finalize = gst_base_text_overlay_finalize;
345 gobject_class->set_property = gst_base_text_overlay_set_property;
346 gobject_class->get_property = gst_base_text_overlay_get_property;
348 gst_element_class_add_pad_template (gstelement_class,
349 gst_static_pad_template_get (&src_template_factory));
350 gst_element_class_add_pad_template (gstelement_class,
351 gst_static_pad_template_get (&video_sink_template_factory));
353 gstelement_class->change_state =
354 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
356 klass->pango_lock = g_slice_new (GMutex);
357 g_mutex_init (klass->pango_lock);
359 klass->get_text = gst_base_text_overlay_get_text;
361 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
362 g_param_spec_string ("text", "text",
363 "Text to be display.", DEFAULT_PROP_TEXT,
364 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
365 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
366 g_param_spec_boolean ("shaded-background", "shaded background",
367 "Whether to shade the background under the text area",
368 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
369 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING_VALUE,
370 g_param_spec_uint ("shading-value", "background shading value",
371 "Shading value to apply if shaded-background is true", 1, 255,
372 DEFAULT_PROP_SHADING_VALUE,
373 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
374 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
375 g_param_spec_enum ("valignment", "vertical alignment",
376 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
377 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
378 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
379 g_param_spec_enum ("halignment", "horizontal alignment",
380 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
381 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
382 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
383 g_param_spec_int ("xpad", "horizontal paddding",
384 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
385 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
386 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
387 g_param_spec_int ("ypad", "vertical padding",
388 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
389 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
390 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
391 g_param_spec_int ("deltax", "X position modifier",
392 "Shift X position to the left or to the right. Unit is pixels.",
393 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
394 GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
395 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
396 g_param_spec_int ("deltay", "Y position modifier",
397 "Shift Y position up or down. Unit is pixels.",
398 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAY,
399 GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
402 * GstBaseTextOverlay:text-x:
404 * Resulting X position of font rendering.
406 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_X,
407 g_param_spec_int ("text-x", "horizontal position.",
408 "Resulting X position of font rendering.", -G_MAXINT,
409 G_MAXINT, DEFAULT_PROP_TEXT_X, G_PARAM_READABLE));
412 * GstBaseTextOverlay:text-y:
414 * Resulting Y position of font rendering.
416 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_Y,
417 g_param_spec_int ("text-y", "vertical position",
418 "Resulting X position of font rendering.", -G_MAXINT,
419 G_MAXINT, DEFAULT_PROP_TEXT_Y, G_PARAM_READABLE));
422 * GstBaseTextOverlay:text-width:
424 * Resulting width of font rendering.
426 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_WIDTH,
427 g_param_spec_uint ("text-width", "width",
428 "Resulting width of font rendering",
429 0, G_MAXINT, DEFAULT_PROP_TEXT_WIDTH, G_PARAM_READABLE));
432 * GstBaseTextOverlay:text-height:
434 * Resulting height of font rendering.
436 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_HEIGHT,
437 g_param_spec_uint ("text-height", "height",
438 "Resulting height of font rendering", 0,
439 G_MAXINT, DEFAULT_PROP_TEXT_HEIGHT, G_PARAM_READABLE));
442 * GstBaseTextOverlay:xpos:
444 * Horizontal position of the rendered text when using positioned alignment.
446 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
447 g_param_spec_double ("xpos", "horizontal position",
448 "Horizontal position when using position alignment", -G_MAXDOUBLE,
449 G_MAXDOUBLE, DEFAULT_PROP_XPOS,
450 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
452 * GstBaseTextOverlay:ypos:
454 * Vertical position of the rendered text when using positioned alignment.
456 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
457 g_param_spec_double ("ypos", "vertical position",
458 "Vertical position when using position alignment", -G_MAXDOUBLE,
459 G_MAXDOUBLE, DEFAULT_PROP_YPOS,
460 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
461 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
462 g_param_spec_enum ("wrap-mode", "wrap mode",
463 "Whether to wrap the text and if so how.",
464 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
465 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
466 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
467 g_param_spec_string ("font-desc", "font description",
468 "Pango font description of font to be used for rendering. "
469 "See documentation of pango_font_description_from_string "
470 "for syntax.", DEFAULT_PROP_FONT_DESC,
471 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
473 * GstBaseTextOverlay:color:
475 * Color of the rendered text.
477 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
478 g_param_spec_uint ("color", "Color",
479 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
481 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
483 * GstTextOverlay:outline-color:
485 * Color of the outline of the rendered text.
487 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
488 g_param_spec_uint ("outline-color", "Text Outline Color",
489 "Color to use for outline the text (big-endian ARGB).", 0,
490 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
491 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
494 * GstBaseTextOverlay:line-alignment:
496 * Alignment of text lines relative to each other (for multi-line text)
498 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
499 g_param_spec_enum ("line-alignment", "line alignment",
500 "Alignment of text lines relative to each other.",
501 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
502 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
504 * GstBaseTextOverlay:silent:
506 * If set, no text is rendered. Useful to switch off text rendering
507 * temporarily without removing the textoverlay element from the pipeline.
509 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
510 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
511 g_param_spec_boolean ("silent", "silent",
512 "Whether to render the text string",
514 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
516 * GstBaseTextOverlay:draw-shadow:
518 * If set, a text shadow is drawn.
522 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_SHADOW,
523 g_param_spec_boolean ("draw-shadow", "draw-shadow",
524 "Whether to draw shadow",
525 DEFAULT_PROP_DRAW_SHADOW,
526 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
528 * GstBaseTextOverlay:draw-outline:
530 * If set, an outline is drawn.
534 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_OUTLINE,
535 g_param_spec_boolean ("draw-outline", "draw-outline",
536 "Whether to draw outline",
537 DEFAULT_PROP_DRAW_OUTLINE,
538 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
540 * GstBaseTextOverlay:wait-text:
542 * If set, the video will block until a subtitle is received on the text pad.
543 * If video and subtitles are sent in sync, like from the same demuxer, this
544 * property should be set.
546 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
547 g_param_spec_boolean ("wait-text", "Wait Text",
548 "Whether to wait for subtitles",
549 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
551 g_object_class_install_property (G_OBJECT_CLASS (klass),
552 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
553 "Automatically adjust font size to screen-size.",
554 DEFAULT_PROP_AUTO_ADJUST_SIZE,
555 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
557 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
558 g_param_spec_boolean ("vertical-render", "vertical render",
559 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
560 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
564 gst_base_text_overlay_finalize (GObject * object)
566 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
568 g_free (overlay->default_text);
570 if (overlay->composition) {
571 gst_video_overlay_composition_unref (overlay->composition);
572 overlay->composition = NULL;
575 if (overlay->text_image) {
576 gst_buffer_unref (overlay->text_image);
577 overlay->text_image = NULL;
580 if (overlay->layout) {
581 g_object_unref (overlay->layout);
582 overlay->layout = NULL;
585 if (overlay->text_buffer) {
586 gst_buffer_unref (overlay->text_buffer);
587 overlay->text_buffer = NULL;
590 g_mutex_clear (&overlay->lock);
591 g_cond_clear (&overlay->cond);
593 G_OBJECT_CLASS (parent_class)->finalize (object);
597 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
598 GstBaseTextOverlayClass * klass)
600 GstPadTemplate *template;
601 PangoFontDescription *desc;
604 template = gst_static_pad_template_get (&video_sink_template_factory);
605 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
606 gst_object_unref (template);
607 gst_pad_set_event_function (overlay->video_sinkpad,
608 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
609 gst_pad_set_chain_function (overlay->video_sinkpad,
610 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
611 gst_pad_set_query_function (overlay->video_sinkpad,
612 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
613 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
614 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
617 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
621 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
623 gst_pad_set_event_function (overlay->text_sinkpad,
624 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
625 gst_pad_set_chain_function (overlay->text_sinkpad,
626 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
627 gst_pad_set_link_function (overlay->text_sinkpad,
628 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
629 gst_pad_set_unlink_function (overlay->text_sinkpad,
630 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
631 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
635 template = gst_static_pad_template_get (&src_template_factory);
636 overlay->srcpad = gst_pad_new_from_template (template, "src");
637 gst_object_unref (template);
638 gst_pad_set_event_function (overlay->srcpad,
639 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
640 gst_pad_set_query_function (overlay->srcpad,
641 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
642 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
644 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
646 pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
647 (overlay)->pango_context);
649 pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
650 (overlay)->pango_context);
651 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
653 overlay->color = DEFAULT_PROP_COLOR;
654 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
655 overlay->halign = DEFAULT_PROP_HALIGNMENT;
656 overlay->valign = DEFAULT_PROP_VALIGNMENT;
657 overlay->xpad = DEFAULT_PROP_XPAD;
658 overlay->ypad = DEFAULT_PROP_YPAD;
659 overlay->deltax = DEFAULT_PROP_DELTAX;
660 overlay->deltay = DEFAULT_PROP_DELTAY;
661 overlay->xpos = DEFAULT_PROP_XPOS;
662 overlay->ypos = DEFAULT_PROP_YPOS;
664 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
666 overlay->want_shading = DEFAULT_PROP_SHADING;
667 overlay->shading_value = DEFAULT_PROP_SHADING_VALUE;
668 overlay->silent = DEFAULT_PROP_SILENT;
669 overlay->draw_shadow = DEFAULT_PROP_DRAW_SHADOW;
670 overlay->draw_outline = DEFAULT_PROP_DRAW_OUTLINE;
671 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
672 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
674 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
675 overlay->need_render = TRUE;
676 overlay->text_image = NULL;
677 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
679 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
680 pango_layout_set_alignment (overlay->layout,
681 (PangoAlignment) overlay->line_align);
683 overlay->text_buffer = NULL;
684 overlay->text_linked = FALSE;
686 overlay->composition = NULL;
687 overlay->upstream_composition = NULL;
692 overlay->window_width = 1;
693 overlay->window_height = 1;
695 overlay->text_width = DEFAULT_PROP_TEXT_WIDTH;
696 overlay->text_height = DEFAULT_PROP_TEXT_HEIGHT;
698 overlay->text_x = DEFAULT_PROP_TEXT_X;
699 overlay->text_y = DEFAULT_PROP_TEXT_Y;
701 overlay->render_width = 1;
702 overlay->render_height = 1;
703 overlay->render_scale = 1.0l;
705 g_mutex_init (&overlay->lock);
706 g_cond_init (&overlay->cond);
707 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
708 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
712 gst_base_text_overlay_set_wrap_mode (GstBaseTextOverlay * overlay, gint width)
714 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
715 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
716 pango_layout_set_width (overlay->layout, -1);
718 width = width * PANGO_SCALE;
720 GST_DEBUG_OBJECT (overlay, "Set layout width %d", width);
721 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
722 pango_layout_set_width (overlay->layout, width);
725 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
729 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
731 GstStructure *structure;
734 structure = gst_caps_get_structure (caps, 0);
735 format = gst_structure_get_string (structure, "format");
736 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
741 /* only negotiate/query video overlay composition support for now */
743 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay, GstCaps * caps)
745 gboolean upstream_has_meta = FALSE;
746 gboolean caps_has_meta = FALSE;
747 gboolean alloc_has_meta = FALSE;
748 gboolean attach = FALSE;
752 GstCaps *overlay_caps;
756 GST_DEBUG_OBJECT (overlay, "performing negotiation");
758 /* Clear any pending reconfigure to avoid negotiating twice */
759 gst_pad_check_reconfigure (overlay->srcpad);
762 caps = gst_pad_get_current_caps (overlay->video_sinkpad);
766 if (!caps || gst_caps_is_empty (caps))
769 /* Check if upstream caps have meta */
770 if ((f = gst_caps_get_features (caps, 0))) {
771 upstream_has_meta = gst_caps_features_contains (f,
772 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
775 /* Initialize dimensions */
776 width = overlay->width;
777 height = overlay->height;
779 if (upstream_has_meta) {
780 overlay_caps = gst_caps_ref (caps);
784 /* BaseTransform requires caps for the allocation query to work */
785 overlay_caps = gst_caps_copy (caps);
786 f = gst_caps_get_features (overlay_caps, 0);
787 gst_caps_features_add (f,
788 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
790 /* Then check if downstream accept overlay composition in caps */
791 /* FIXME: We should probably check if downstream *prefers* the
792 * overlay meta, and only enforce usage of it if we can't handle
793 * the format ourselves and thus would have to drop the overlays.
794 * Otherwise we should prefer what downstream wants here.
796 peercaps = gst_pad_peer_query_caps (overlay->srcpad, NULL);
797 caps_has_meta = gst_caps_can_intersect (peercaps, overlay_caps);
798 gst_caps_unref (peercaps);
800 GST_DEBUG ("caps have overlay meta %d", caps_has_meta);
803 if (upstream_has_meta || caps_has_meta) {
804 /* Send caps immediatly, it's needed by GstBaseTransform to get a reply
805 * from allocation query */
806 ret = gst_pad_set_caps (overlay->srcpad, overlay_caps);
808 /* First check if the allocation meta has compositon */
809 query = gst_query_new_allocation (overlay_caps, FALSE);
811 if (!gst_pad_peer_query (overlay->srcpad, query)) {
812 /* no problem, we use the query defaults */
813 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
815 /* In case we were flushing, mark reconfigure and fail this method,
816 * will make it retry */
817 if (overlay->video_flushing)
821 alloc_has_meta = gst_query_find_allocation_meta (query,
822 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, &alloc_index);
824 GST_DEBUG ("sink alloc has overlay meta %d", alloc_has_meta);
826 if (alloc_has_meta) {
827 const GstStructure *params;
829 gst_query_parse_nth_allocation_meta (query, alloc_index, ¶ms);
831 if (gst_structure_get (params, "width", G_TYPE_UINT, &width,
832 "height", G_TYPE_UINT, &height, NULL)) {
833 GST_DEBUG ("received window size: %dx%d", width, height);
834 g_assert (width != 0 && height != 0);
839 gst_query_unref (query);
842 /* Update render size if needed */
843 overlay->window_width = width;
844 overlay->window_height = height;
845 gst_base_text_overlay_update_render_size (overlay);
847 /* For backward compatbility, we will prefer bliting if downstream
848 * allocation does not support the meta. In other case we will prefer
849 * attaching, and will fail the negotiation in the unlikely case we are
850 * force to blit, but format isn't supported. */
852 if (upstream_has_meta) {
854 } else if (caps_has_meta) {
855 if (alloc_has_meta) {
858 /* Don't attach unless we cannot handle the format */
859 attach = !gst_base_text_overlay_can_handle_caps (caps);
862 ret = gst_base_text_overlay_can_handle_caps (caps);
865 /* If we attach, then pick the overlay caps */
867 GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, overlay_caps);
868 /* Caps where already sent */
870 GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps);
871 ret = gst_pad_set_caps (overlay->srcpad, caps);
874 overlay->attach_compo_to_buffer = attach;
877 GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure");
878 gst_pad_mark_reconfigure (overlay->srcpad);
881 gst_caps_unref (overlay_caps);
882 gst_caps_unref (caps);
889 gst_caps_unref (caps);
895 gst_base_text_overlay_can_handle_caps (GstCaps * incaps)
899 static GstStaticCaps static_caps = GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
901 caps = gst_static_caps_get (&static_caps);
902 ret = gst_caps_is_subset (incaps, caps);
903 gst_caps_unref (caps);
909 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
912 gboolean ret = FALSE;
914 if (!gst_video_info_from_caps (&info, caps))
917 /* Render again if size have changed */
918 if (GST_VIDEO_INFO_WIDTH (&info) != GST_VIDEO_INFO_WIDTH (&overlay->info) ||
919 GST_VIDEO_INFO_HEIGHT (&info) != GST_VIDEO_INFO_HEIGHT (&overlay->info))
920 overlay->need_render = TRUE;
922 overlay->info = info;
923 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
924 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
925 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
927 ret = gst_base_text_overlay_negotiate (overlay, caps);
929 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
931 if (!overlay->attach_compo_to_buffer &&
932 !gst_base_text_overlay_can_handle_caps (caps)) {
933 GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps);
936 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
943 GST_DEBUG_OBJECT (overlay, "could not parse caps");
949 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
950 const GValue * value, GParamSpec * pspec)
952 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
954 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
957 g_free (overlay->default_text);
958 overlay->default_text = g_value_dup_string (value);
961 overlay->want_shading = g_value_get_boolean (value);
964 overlay->xpad = g_value_get_int (value);
967 overlay->ypad = g_value_get_int (value);
970 overlay->deltax = g_value_get_int (value);
973 overlay->deltay = g_value_get_int (value);
976 overlay->xpos = g_value_get_double (value);
979 overlay->ypos = g_value_get_double (value);
981 case PROP_VALIGNMENT:
982 overlay->valign = g_value_get_enum (value);
984 case PROP_HALIGNMENT:
985 overlay->halign = g_value_get_enum (value);
988 overlay->wrap_mode = g_value_get_enum (value);
992 PangoFontDescription *desc;
993 const gchar *fontdesc_str;
995 fontdesc_str = g_value_get_string (value);
996 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
997 desc = pango_font_description_from_string (fontdesc_str);
999 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
1000 pango_layout_set_font_description (overlay->layout, desc);
1001 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
1002 pango_font_description_free (desc);
1004 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
1007 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1011 overlay->color = g_value_get_uint (value);
1013 case PROP_OUTLINE_COLOR:
1014 overlay->outline_color = g_value_get_uint (value);
1017 overlay->silent = g_value_get_boolean (value);
1019 case PROP_DRAW_SHADOW:
1020 overlay->draw_shadow = g_value_get_boolean (value);
1022 case PROP_DRAW_OUTLINE:
1023 overlay->draw_outline = g_value_get_boolean (value);
1025 case PROP_LINE_ALIGNMENT:
1026 overlay->line_align = g_value_get_enum (value);
1027 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1028 pango_layout_set_alignment (overlay->layout,
1029 (PangoAlignment) overlay->line_align);
1030 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1032 case PROP_WAIT_TEXT:
1033 overlay->wait_text = g_value_get_boolean (value);
1035 case PROP_AUTO_ADJUST_SIZE:
1036 overlay->auto_adjust_size = g_value_get_boolean (value);
1038 case PROP_VERTICAL_RENDER:
1039 overlay->use_vertical_render = g_value_get_boolean (value);
1040 if (overlay->use_vertical_render) {
1041 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1042 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1043 overlay->line_align = GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT;
1044 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1045 pango_layout_set_alignment (overlay->layout,
1046 (PangoAlignment) overlay->line_align);
1047 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1050 case PROP_SHADING_VALUE:
1051 overlay->shading_value = g_value_get_uint (value);
1054 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1058 overlay->need_render = TRUE;
1059 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1063 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
1064 GValue * value, GParamSpec * pspec)
1066 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
1068 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1071 g_value_set_string (value, overlay->default_text);
1074 g_value_set_boolean (value, overlay->want_shading);
1077 g_value_set_int (value, overlay->xpad);
1080 g_value_set_int (value, overlay->ypad);
1083 g_value_set_int (value, overlay->deltax);
1086 g_value_set_int (value, overlay->deltay);
1089 g_value_set_double (value, overlay->xpos);
1092 g_value_set_double (value, overlay->ypos);
1094 case PROP_VALIGNMENT:
1095 g_value_set_enum (value, overlay->valign);
1097 case PROP_HALIGNMENT:
1098 g_value_set_enum (value, overlay->halign);
1100 case PROP_WRAP_MODE:
1101 g_value_set_enum (value, overlay->wrap_mode);
1104 g_value_set_boolean (value, overlay->silent);
1106 case PROP_DRAW_SHADOW:
1107 g_value_set_boolean (value, overlay->draw_shadow);
1109 case PROP_DRAW_OUTLINE:
1110 g_value_set_boolean (value, overlay->draw_outline);
1112 case PROP_LINE_ALIGNMENT:
1113 g_value_set_enum (value, overlay->line_align);
1115 case PROP_WAIT_TEXT:
1116 g_value_set_boolean (value, overlay->wait_text);
1118 case PROP_AUTO_ADJUST_SIZE:
1119 g_value_set_boolean (value, overlay->auto_adjust_size);
1121 case PROP_VERTICAL_RENDER:
1122 g_value_set_boolean (value, overlay->use_vertical_render);
1125 g_value_set_uint (value, overlay->color);
1127 case PROP_OUTLINE_COLOR:
1128 g_value_set_uint (value, overlay->outline_color);
1130 case PROP_SHADING_VALUE:
1131 g_value_set_uint (value, overlay->shading_value);
1133 case PROP_FONT_DESC:
1135 const PangoFontDescription *desc;
1137 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1138 desc = pango_layout_get_font_description (overlay->layout);
1140 g_value_set_string (value, "");
1142 g_value_take_string (value, pango_font_description_to_string (desc));
1144 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1148 g_value_set_int (value, overlay->text_x);
1151 g_value_set_int (value, overlay->text_y);
1153 case PROP_TEXT_WIDTH:
1154 g_value_set_uint (value, overlay->text_width);
1156 case PROP_TEXT_HEIGHT:
1157 g_value_set_uint (value, overlay->text_height);
1160 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1164 overlay->need_render = TRUE;
1165 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1169 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1172 gboolean ret = FALSE;
1173 GstBaseTextOverlay *overlay;
1175 overlay = GST_BASE_TEXT_OVERLAY (parent);
1177 switch (GST_QUERY_TYPE (query)) {
1178 case GST_QUERY_CAPS:
1180 GstCaps *filter, *caps;
1182 gst_query_parse_caps (query, &filter);
1183 caps = gst_base_text_overlay_get_src_caps (pad, overlay, filter);
1184 gst_query_set_caps_result (query, caps);
1185 gst_caps_unref (caps);
1190 ret = gst_pad_query_default (pad, parent, query);
1198 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay)
1200 gdouble video_aspect = (gdouble) overlay->width / (gdouble) overlay->height;
1201 gdouble window_aspect = (gdouble) overlay->window_width /
1202 (gdouble) overlay->window_height;
1204 guint text_buffer_width = 0;
1205 guint text_buffer_height = 0;
1207 if (video_aspect >= window_aspect) {
1208 text_buffer_width = overlay->window_width;
1209 text_buffer_height = window_aspect * overlay->window_height / video_aspect;
1210 } else if (video_aspect < window_aspect) {
1211 text_buffer_width = video_aspect * overlay->window_width / window_aspect;
1212 text_buffer_height = overlay->window_height;
1215 if ((overlay->render_width == text_buffer_width) &&
1216 (overlay->render_height == text_buffer_height))
1219 overlay->need_render = TRUE;
1220 overlay->render_width = text_buffer_width;
1221 overlay->render_height = text_buffer_height;
1222 overlay->render_scale = (gdouble) overlay->render_width /
1223 (gdouble) overlay->width;
1225 GST_DEBUG ("updating render dimensions %dx%d from stream %dx%d, window %dx%d "
1226 "and render scale %f", overlay->render_width, overlay->render_height,
1227 overlay->width, overlay->height, overlay->window_width,
1228 overlay->window_height, overlay->render_scale);
1232 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1235 GstBaseTextOverlay *overlay;
1238 overlay = GST_BASE_TEXT_OVERLAY (parent);
1240 if (overlay->text_linked) {
1241 gst_event_ref (event);
1242 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1243 gst_pad_push_event (overlay->text_sinkpad, event);
1245 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1252 * gst_base_text_overlay_add_feature_and_intersect:
1254 * Creates a new #GstCaps containing the (given caps +
1255 * given caps feature) + (given caps intersected by the
1258 * Returns: the new #GstCaps
1261 gst_base_text_overlay_add_feature_and_intersect (GstCaps * caps,
1262 const gchar * feature, GstCaps * filter)
1267 new_caps = gst_caps_copy (caps);
1269 caps_size = gst_caps_get_size (new_caps);
1270 for (i = 0; i < caps_size; i++) {
1271 GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
1273 if (!gst_caps_features_is_any (features)) {
1274 gst_caps_features_add (features, feature);
1278 gst_caps_append (new_caps, gst_caps_intersect_full (caps,
1279 filter, GST_CAPS_INTERSECT_FIRST));
1285 * gst_base_text_overlay_intersect_by_feature:
1287 * Creates a new #GstCaps based on the following filtering rule.
1289 * For each individual caps contained in given caps, if the
1290 * caps uses the given caps feature, keep a version of the caps
1291 * with the feature and an another one without. Otherwise, intersect
1292 * the caps with the given filter.
1294 * Returns: the new #GstCaps
1297 gst_base_text_overlay_intersect_by_feature (GstCaps * caps,
1298 const gchar * feature, GstCaps * filter)
1303 new_caps = gst_caps_new_empty ();
1305 caps_size = gst_caps_get_size (caps);
1306 for (i = 0; i < caps_size; i++) {
1307 GstStructure *caps_structure = gst_caps_get_structure (caps, i);
1308 GstCapsFeatures *caps_features =
1309 gst_caps_features_copy (gst_caps_get_features (caps, i));
1310 GstCaps *filtered_caps;
1311 GstCaps *simple_caps =
1312 gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
1313 gst_caps_set_features (simple_caps, 0, caps_features);
1315 if (gst_caps_features_contains (caps_features, feature)) {
1316 gst_caps_append (new_caps, gst_caps_copy (simple_caps));
1318 gst_caps_features_remove (caps_features, feature);
1319 filtered_caps = gst_caps_ref (simple_caps);
1321 filtered_caps = gst_caps_intersect_full (simple_caps, filter,
1322 GST_CAPS_INTERSECT_FIRST);
1325 gst_caps_unref (simple_caps);
1326 gst_caps_append (new_caps, filtered_caps);
1333 gst_base_text_overlay_get_videosink_caps (GstPad * pad,
1334 GstBaseTextOverlay * overlay, GstCaps * filter)
1336 GstPad *srcpad = overlay->srcpad;
1337 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1339 if (G_UNLIKELY (!overlay))
1340 return gst_pad_get_pad_template_caps (pad);
1343 /* filter caps + composition feature + filter caps
1344 * filtered by the software caps. */
1345 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1346 overlay_filter = gst_base_text_overlay_add_feature_and_intersect (filter,
1347 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1348 gst_caps_unref (sw_caps);
1350 GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
1354 peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
1357 gst_caps_unref (overlay_filter);
1361 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
1363 if (gst_caps_is_any (peer_caps)) {
1364 /* if peer returns ANY caps, return filtered src pad template caps */
1365 caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
1368 /* duplicate caps which contains the composition into one version with
1369 * the meta and one without. Filter the other caps by the software caps */
1370 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1371 caps = gst_base_text_overlay_intersect_by_feature (peer_caps,
1372 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1373 gst_caps_unref (sw_caps);
1376 gst_caps_unref (peer_caps);
1379 /* no peer, our padtemplate is enough then */
1380 caps = gst_pad_get_pad_template_caps (pad);
1384 GstCaps *intersection = gst_caps_intersect_full (filter, caps,
1385 GST_CAPS_INTERSECT_FIRST);
1386 gst_caps_unref (caps);
1387 caps = intersection;
1390 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1396 gst_base_text_overlay_get_src_caps (GstPad * pad, GstBaseTextOverlay * overlay,
1399 GstPad *sinkpad = overlay->video_sinkpad;
1400 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1402 if (G_UNLIKELY (!overlay))
1403 return gst_pad_get_pad_template_caps (pad);
1406 /* duplicate filter caps which contains the composition into one version
1407 * with the meta and one without. Filter the other caps by the software
1409 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1411 gst_base_text_overlay_intersect_by_feature (filter,
1412 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1413 gst_caps_unref (sw_caps);
1416 peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
1419 gst_caps_unref (overlay_filter);
1423 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
1425 if (gst_caps_is_any (peer_caps)) {
1427 /* if peer returns ANY caps, return filtered sink pad template caps */
1428 caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
1432 /* return upstream caps + composition feature + upstream caps
1433 * filtered by the software caps. */
1434 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1435 caps = gst_base_text_overlay_add_feature_and_intersect (peer_caps,
1436 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1437 gst_caps_unref (sw_caps);
1440 gst_caps_unref (peer_caps);
1443 /* no peer, our padtemplate is enough then */
1444 caps = gst_pad_get_pad_template_caps (pad);
1448 GstCaps *intersection;
1451 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1452 gst_caps_unref (caps);
1453 caps = intersection;
1455 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1461 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1462 PangoFontDescription * desc)
1464 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1465 overlay->shadow_offset = (double) (font_size) / 13.0;
1466 overlay->outline_offset = (double) (font_size) / 15.0;
1467 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1468 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1472 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1473 gint * xpos, gint * ypos)
1477 width = overlay->logical_rect.width;
1478 height = overlay->logical_rect.height;
1480 *xpos = overlay->ink_rect.x - overlay->logical_rect.x;
1481 switch (overlay->halign) {
1482 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1483 *xpos += overlay->xpad;
1484 *xpos = MAX (0, *xpos);
1486 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1487 *xpos += (overlay->width - width) / 2;
1489 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1490 *xpos += overlay->width - width - overlay->xpad;
1491 *xpos = MIN (overlay->width - overlay->ink_rect.width, *xpos);
1493 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1494 *xpos += (gint) (overlay->width * overlay->xpos) - width / 2;
1499 *xpos += overlay->deltax;
1501 *ypos = overlay->ink_rect.y - overlay->logical_rect.y;
1502 switch (overlay->valign) {
1503 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1504 /* This will be the same as baseline, if there is enough padding,
1505 * otherwise it will avoid clipping the text */
1506 *ypos += overlay->height - height - overlay->ypad;
1507 *ypos = MIN (overlay->height - overlay->ink_rect.height, *ypos);
1509 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1510 *ypos += overlay->height - height - overlay->ypad;
1511 /* Don't clip, this would not respect the base line */
1513 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1514 *ypos += overlay->ypad;
1515 *ypos = MAX (0.0, *ypos);
1517 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1518 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1520 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1521 *ypos = (overlay->height - height) / 2;
1524 *ypos = overlay->ypad;
1527 *ypos += overlay->deltay;
1529 overlay->text_x = *xpos;
1530 overlay->text_y = *ypos;
1532 GST_DEBUG_OBJECT (overlay, "Placing overlay at (%d, %d)", *xpos, *ypos);
1536 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1539 GstVideoOverlayRectangle *rectangle;
1541 if (overlay->text_image && overlay->text_width != 1) {
1542 gint render_width, render_height;
1544 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1546 render_width = overlay->ink_rect.width;
1547 render_height = overlay->ink_rect.height;
1549 GST_DEBUG ("updating composition for '%s' with window size %dx%d, "
1550 "buffer size %dx%d, render size %dx%d and position (%d, %d)",
1551 overlay->default_text, overlay->window_width, overlay->window_height,
1552 overlay->text_width, overlay->text_height, render_width,
1553 render_height, xpos, ypos);
1555 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1556 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1557 overlay->text_width, overlay->text_height);
1559 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1560 xpos, ypos, render_width, render_height,
1561 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1563 if (overlay->composition)
1564 gst_video_overlay_composition_unref (overlay->composition);
1566 overlay->composition = gst_video_overlay_composition_new (rectangle);
1567 gst_video_overlay_rectangle_unref (rectangle);
1569 if (overlay->upstream_composition) {
1570 guint num_overlays =
1571 gst_video_overlay_composition_n_rectangles
1572 (overlay->upstream_composition);
1574 for (guint i = 0; i < num_overlays; i++) {
1575 GstVideoOverlayRectangle *rectangle;
1577 gst_video_overlay_composition_get_rectangle
1578 (overlay->upstream_composition, i);
1579 gst_video_overlay_composition_add_rectangle (overlay->composition,
1584 } else if (overlay->composition) {
1585 gst_video_overlay_composition_unref (overlay->composition);
1586 overlay->composition = NULL;
1591 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1593 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1601 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1602 const gchar * string, gint textlen)
1605 cairo_surface_t *surface;
1606 PangoRectangle ink_rect, logical_rect;
1607 cairo_matrix_t cairo_matrix;
1608 gint unscaled_width, unscaled_height;
1610 gboolean full_width = FALSE;
1611 double scalef = 1.0;
1613 gdouble shadow_offset = 0.0;
1614 gdouble outline_offset = 0.0;
1615 gint xpad = 0, ypad = 0;
1619 g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1621 if (overlay->auto_adjust_size) {
1622 /* 640 pixel is default */
1623 scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1626 if (overlay->draw_shadow)
1627 shadow_offset = ceil (overlay->shadow_offset);
1629 /* This value is uses as cairo line width, which is the diameter of a pen
1630 * that is circular. That's why only half of it is used to offset */
1631 if (overlay->draw_outline)
1632 outline_offset = ceil (overlay->outline_offset);
1634 if (overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_LEFT ||
1635 overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT)
1636 xpad = overlay->xpad;
1638 if (overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_TOP ||
1639 overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM)
1640 ypad = overlay->ypad;
1642 pango_layout_set_width (overlay->layout, -1);
1643 /* set text on pango layout */
1644 pango_layout_set_markup (overlay->layout, string, textlen);
1646 /* get subtitle image size */
1647 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1649 unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1650 width = ceil (unscaled_width * scalef);
1653 * subtitle image width can be larger then overlay width
1654 * so rearrange overlay wrap mode.
1656 if (overlay->use_vertical_render) {
1657 if (width + ypad > overlay->height) {
1658 width = overlay->height - ypad;
1661 } else if (width + xpad > overlay->width) {
1662 width = overlay->width - xpad;
1667 unscaled_width = width / scalef;
1668 gst_base_text_overlay_set_wrap_mode (overlay,
1669 unscaled_width - shadow_offset - outline_offset);
1670 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1672 unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1673 width = ceil (unscaled_width * scalef);
1676 unscaled_height = ink_rect.height + shadow_offset + outline_offset;
1677 height = ceil (unscaled_height * scalef);
1679 if (overlay->use_vertical_render) {
1680 if (height + xpad > overlay->width) {
1681 height = overlay->width - xpad;
1682 unscaled_height = width / scalef;
1684 } else if (height + ypad > overlay->height) {
1685 height = overlay->height - ypad;
1686 unscaled_height = height / scalef;
1689 GST_DEBUG_OBJECT (overlay, "Rendering with ink rect (%d, %d) %dx%d and "
1690 "logical rect (%d, %d) %dx%d", ink_rect.x, ink_rect.y, ink_rect.width,
1691 ink_rect.height, logical_rect.x, logical_rect.y, logical_rect.width,
1692 logical_rect.height);
1693 GST_DEBUG_OBJECT (overlay, "Rendering with width %d and height %d "
1694 "(shadow %f, outline %f)", unscaled_width, unscaled_height,
1695 shadow_offset, outline_offset);
1698 /* Save and scale the rectangles so get_pos() can place the text */
1699 overlay->ink_rect.x =
1700 ceil ((ink_rect.x - ceil (outline_offset / 2.0l)) * scalef);
1701 overlay->ink_rect.y =
1702 ceil ((ink_rect.y - ceil (outline_offset / 2.0l)) * scalef);
1703 overlay->ink_rect.width = width;
1704 overlay->ink_rect.height = height;
1706 overlay->logical_rect.x =
1707 ceil ((logical_rect.x - ceil (outline_offset / 2.0l)) * scalef);
1708 overlay->logical_rect.y =
1709 ceil ((logical_rect.y - ceil (outline_offset / 2.0l)) * scalef);
1710 overlay->logical_rect.width =
1711 ceil ((logical_rect.width + shadow_offset + outline_offset) * scalef);
1712 overlay->logical_rect.height =
1713 ceil ((logical_rect.height + shadow_offset + outline_offset) * scalef);
1715 /* flip the rectangle if doing vertical render */
1716 if (overlay->use_vertical_render) {
1717 PangoRectangle tmp = overlay->ink_rect;
1719 overlay->ink_rect.x = tmp.y;
1720 overlay->ink_rect.y = tmp.x;
1721 overlay->ink_rect.width = tmp.height;
1722 overlay->ink_rect.height = tmp.width;
1723 /* We want the top left corect, but we now have the top right */
1724 overlay->ink_rect.x += overlay->ink_rect.width;
1726 tmp = overlay->logical_rect;
1727 overlay->logical_rect.x = tmp.y;
1728 overlay->logical_rect.y = tmp.x;
1729 overlay->logical_rect.width = tmp.height;
1730 overlay->logical_rect.height = tmp.width;
1731 overlay->logical_rect.x += overlay->logical_rect.width;
1734 /* scale to reported window size */
1735 width = ceil (width * overlay->render_scale);
1736 height = ceil (height * overlay->render_scale);
1737 scalef *= overlay->render_scale;
1739 if (width <= 0 || height <= 0) {
1740 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1741 GST_DEBUG_OBJECT (overlay,
1742 "Overlay is outside video frame. Skipping text rendering");
1746 if (unscaled_height <= 0 || unscaled_width <= 0) {
1747 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1748 GST_DEBUG_OBJECT (overlay,
1749 "Overlay is outside video frame. Skipping text rendering");
1752 /* Prepare the transformation matrix. Note that the transformation happens
1753 * in reverse order. So for horizontal text, we will translate and then
1754 * scale. This is important to understand which scale shall be used. */
1755 cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1757 if (overlay->use_vertical_render) {
1760 /* tranlate to the center of the image, rotate, and tranlate the rotated
1761 * image back to the right place */
1762 cairo_matrix_translate (&cairo_matrix, unscaled_height / 2.0l,
1763 unscaled_width / 2.0l);
1764 /* 90 degree clockwise rotation which is PI / 2 in radiants */
1765 cairo_matrix_rotate (&cairo_matrix, G_PI_2);
1766 cairo_matrix_translate (&cairo_matrix, -(unscaled_width / 2.0l),
1767 -(unscaled_height / 2.0l));
1769 /* Swap width and height */
1775 cairo_matrix_translate (&cairo_matrix,
1776 ceil (outline_offset / 2.0l) - ink_rect.x,
1777 ceil (outline_offset / 2.0l) - ink_rect.y);
1779 /* reallocate overlay buffer */
1780 buffer = gst_buffer_new_and_alloc (4 * width * height);
1781 gst_buffer_replace (&overlay->text_image, buffer);
1782 gst_buffer_unref (buffer);
1784 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1785 surface = cairo_image_surface_create_for_data (map.data,
1786 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1787 cr = cairo_create (surface);
1790 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1793 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1795 /* apply transformations */
1796 cairo_set_matrix (cr, &cairo_matrix);
1798 /* FIXME: We use show_layout everywhere except for the surface
1799 * because it's really faster and internally does all kinds of
1800 * caching. Unfortunately we have to paint to a cairo path for
1801 * the outline and this is slow. Once Pango supports user fonts
1802 * we should use them, see
1803 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1805 * Idea would the be, to create a cairo user font that
1806 * does shadow, outline, text painting in the
1807 * render_glyph function.
1810 /* draw shadow text */
1811 if (overlay->draw_shadow) {
1812 PangoAttrList *origin_attr, *filtered_attr, *temp_attr;
1814 /* Store a ref on the original attributes for later restoration */
1816 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1817 /* Take a copy of the original attributes, because pango_attr_list_filter
1818 * modifies the passed list */
1819 temp_attr = pango_attr_list_copy (origin_attr);
1821 pango_attr_list_filter (temp_attr,
1822 gst_text_overlay_filter_foreground_attr, NULL);
1823 pango_attr_list_unref (temp_attr);
1826 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1827 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1828 pango_layout_set_attributes (overlay->layout, filtered_attr);
1829 pango_cairo_show_layout (cr, overlay->layout);
1830 pango_layout_set_attributes (overlay->layout, origin_attr);
1831 pango_attr_list_unref (filtered_attr);
1832 pango_attr_list_unref (origin_attr);
1836 /* draw outline text */
1837 if (overlay->draw_outline) {
1838 a = (overlay->outline_color >> 24) & 0xff;
1839 r = (overlay->outline_color >> 16) & 0xff;
1840 g = (overlay->outline_color >> 8) & 0xff;
1841 b = (overlay->outline_color >> 0) & 0xff;
1844 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1845 cairo_set_line_width (cr, overlay->outline_offset);
1846 pango_cairo_layout_path (cr, overlay->layout);
1851 a = (overlay->color >> 24) & 0xff;
1852 r = (overlay->color >> 16) & 0xff;
1853 g = (overlay->color >> 8) & 0xff;
1854 b = (overlay->color >> 0) & 0xff;
1858 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1859 pango_cairo_show_layout (cr, overlay->layout);
1863 cairo_surface_destroy (surface);
1864 gst_buffer_unmap (buffer, &map);
1866 overlay->text_width = width;
1868 overlay->text_height = height;
1869 g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1871 gst_base_text_overlay_set_composition (overlay);
1875 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1876 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1878 gint i, j, dest_stride;
1881 dest_stride = dest->info.stride[0];
1882 dest_ptr = dest->data[0];
1884 for (i = y0; i < y1; ++i) {
1885 for (j = x0; j < x1; ++j) {
1886 gint y = dest_ptr[(i * dest_stride) + j] - overlay->shading_value;
1888 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1894 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1895 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1898 guint dest_stride, pixel_stride;
1901 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1902 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1903 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1906 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1908 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1911 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1913 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1915 for (i = y0; i < y1; i++) {
1916 for (j = x0; j < x1; j++) {
1920 y_pos = (i * dest_stride) + j * pixel_stride;
1921 y = dest_ptr[y_pos] - overlay->shading_value;
1923 dest_ptr[y_pos] = CLAMP (y, 0, 255);
1928 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1929 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1930 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1932 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1933 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1938 dest_ptr = dest->data[0];
1940 for (i = y0; i < y1; i++) {
1941 for (j = x0; j < x1; j++) {
1944 y_pos = (i * 4 * overlay->width) + j * 4;
1945 for (k = 0; k < 4; k++) {
1946 y = dest_ptr[y_pos + k] - overlay->shading_value;
1947 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1955 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
1956 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1958 const int pstride = 3;
1959 gint y, x, stride, shading_val, tmp;
1962 shading_val = -overlay->shading_value;
1963 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1965 for (y = y0; y < y1; ++y) {
1966 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1967 p += (y * stride) + (x0 * pstride);
1968 for (x = x0; x < x1; ++x) {
1969 tmp = *p + shading_val;
1970 *p++ = CLAMP (tmp, 0, 255);
1971 tmp = *p + shading_val;
1972 *p++ = CLAMP (tmp, 0, 255);
1973 tmp = *p + shading_val;
1974 *p++ = CLAMP (tmp, 0, 255);
1980 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
1981 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1983 gint y, x, stride, shading_val, tmp;
1986 shading_val = -overlay->shading_value;
1987 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1989 /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
1990 for (y = y0; y < y1; ++y) {
1991 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1992 /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
1993 /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
1994 * unlikely anyone will notice.. */
1995 p += (y * stride) + ((x0 / 2) * 3) + 1;
1996 for (x = x0; x < x1; x += 2) {
1997 tmp = *p + shading_val;
1998 *p++ = CLAMP (tmp, 0, 255);
1999 tmp = *p + shading_val;
2000 *p++ = CLAMP (tmp, 0, 255);
2007 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
2008 static inline void \
2009 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
2010 gint x0, gint x1, gint y0, gint y1) \
2015 dest_ptr = dest->data[0];\
2017 for (i = y0; i < y1; i++) {\
2018 for (j = x0; j < x1; j++) {\
2020 y_pos = (i * 4 * overlay->width) + j * 4;\
2021 for (k = OFFSET; k < 3+OFFSET; k++) {\
2022 y = dest_ptr[y_pos + k] - overlay->shading_value;\
2023 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
2028 ARGB_SHADE_FUNCTION (ARGB, 1);
2029 ARGB_SHADE_FUNCTION (ABGR, 1);
2030 ARGB_SHADE_FUNCTION (RGBA, 0);
2031 ARGB_SHADE_FUNCTION (BGRA, 0);
2034 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
2035 const gchar * text, gint textlen)
2039 if (!overlay->need_render) {
2040 GST_DEBUG ("Using previously rendered text.");
2044 /* -1 is the whole string */
2045 if (text != NULL && textlen < 0) {
2046 textlen = strlen (text);
2050 string = g_strndup (text, textlen);
2051 } else { /* empty string */
2052 string = g_strdup (" ");
2054 g_strdelimit (string, "\r\t", ' ');
2055 textlen = strlen (string);
2057 /* FIXME: should we check for UTF-8 here? */
2059 GST_DEBUG ("Rendering '%s'", string);
2060 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
2064 overlay->need_render = FALSE;
2067 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
2072 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
2073 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2075 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
2076 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
2078 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
2079 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
2081 switch (overlay->format) {
2082 case GST_VIDEO_FORMAT_I420:
2083 case GST_VIDEO_FORMAT_YV12:
2084 case GST_VIDEO_FORMAT_NV12:
2085 case GST_VIDEO_FORMAT_NV21:
2086 case GST_VIDEO_FORMAT_Y41B:
2087 case GST_VIDEO_FORMAT_Y42B:
2088 case GST_VIDEO_FORMAT_Y444:
2089 case GST_VIDEO_FORMAT_YUV9:
2090 case GST_VIDEO_FORMAT_YVU9:
2091 case GST_VIDEO_FORMAT_GRAY8:
2092 case GST_VIDEO_FORMAT_A420:
2093 gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
2095 case GST_VIDEO_FORMAT_AYUV:
2096 case GST_VIDEO_FORMAT_UYVY:
2097 case GST_VIDEO_FORMAT_YUY2:
2098 case GST_VIDEO_FORMAT_v308:
2099 gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
2101 case GST_VIDEO_FORMAT_xRGB:
2102 gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
2104 case GST_VIDEO_FORMAT_xBGR:
2105 gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
2107 case GST_VIDEO_FORMAT_BGRx:
2108 gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
2110 case GST_VIDEO_FORMAT_RGBx:
2111 gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
2113 case GST_VIDEO_FORMAT_ARGB:
2114 gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
2116 case GST_VIDEO_FORMAT_ABGR:
2117 gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
2119 case GST_VIDEO_FORMAT_RGBA:
2120 gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
2122 case GST_VIDEO_FORMAT_BGRA:
2123 gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
2125 case GST_VIDEO_FORMAT_BGR:
2126 case GST_VIDEO_FORMAT_RGB:
2127 gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
2129 case GST_VIDEO_FORMAT_IYU1:
2130 gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
2133 GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
2134 gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
2139 static GstFlowReturn
2140 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
2141 GstBuffer * video_frame)
2143 GstVideoFrame frame;
2145 if (overlay->composition == NULL)
2148 if (gst_pad_check_reconfigure (overlay->srcpad))
2149 gst_base_text_overlay_negotiate (overlay, NULL);
2151 video_frame = gst_buffer_make_writable (video_frame);
2153 if (overlay->attach_compo_to_buffer) {
2154 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
2155 gst_buffer_add_video_overlay_composition_meta (video_frame,
2156 overlay->composition);
2157 /* FIXME: emulate shaded background box if want_shading=true */
2161 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
2165 /* shaded background box */
2166 if (overlay->want_shading) {
2169 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
2171 gst_base_text_overlay_shade_background (overlay, &frame,
2172 xpos, xpos + overlay->text_width, ypos, ypos + overlay->text_height);
2175 gst_video_overlay_composition_blend (overlay->composition, &frame);
2177 gst_video_frame_unmap (&frame);
2181 return gst_pad_push (overlay->srcpad, video_frame);
2186 gst_buffer_unref (video_frame);
2187 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
2192 static GstPadLinkReturn
2193 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
2196 GstBaseTextOverlay *overlay;
2198 overlay = GST_BASE_TEXT_OVERLAY (parent);
2199 if (G_UNLIKELY (!overlay))
2200 return GST_PAD_LINK_REFUSED;
2202 GST_DEBUG_OBJECT (overlay, "Text pad linked");
2204 overlay->text_linked = TRUE;
2206 return GST_PAD_LINK_OK;
2210 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
2212 GstBaseTextOverlay *overlay;
2214 /* don't use gst_pad_get_parent() here, will deadlock */
2215 overlay = GST_BASE_TEXT_OVERLAY (parent);
2217 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
2219 overlay->text_linked = FALSE;
2221 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
2225 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
2228 gboolean ret = FALSE;
2229 GstBaseTextOverlay *overlay = NULL;
2231 overlay = GST_BASE_TEXT_OVERLAY (parent);
2233 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2235 switch (GST_EVENT_TYPE (event)) {
2236 case GST_EVENT_CAPS:
2240 gst_event_parse_caps (event, &caps);
2241 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
2242 gst_event_unref (event);
2245 case GST_EVENT_SEGMENT:
2247 const GstSegment *segment;
2249 overlay->text_eos = FALSE;
2251 gst_event_parse_segment (event, &segment);
2253 if (segment->format == GST_FORMAT_TIME) {
2254 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2255 gst_segment_copy_into (segment, &overlay->text_segment);
2256 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
2257 &overlay->text_segment);
2258 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2260 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2261 ("received non-TIME newsegment event on text input"));
2264 gst_event_unref (event);
2267 /* wake up the video chain, it might be waiting for a text buffer or
2268 * a text segment update */
2269 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2270 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2271 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2276 GstClockTime start, duration;
2278 gst_event_parse_gap (event, &start, &duration);
2279 if (GST_CLOCK_TIME_IS_VALID (duration))
2281 /* we do not expect another buffer until after gap,
2282 * so that is our position now */
2283 overlay->text_segment.position = start;
2285 /* wake up the video chain, it might be waiting for a text buffer or
2286 * a text segment update */
2287 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2288 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2289 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2291 gst_event_unref (event);
2295 case GST_EVENT_FLUSH_STOP:
2296 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2297 GST_INFO_OBJECT (overlay, "text flush stop");
2298 overlay->text_flushing = FALSE;
2299 overlay->text_eos = FALSE;
2300 gst_base_text_overlay_pop_text (overlay);
2301 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2302 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2303 gst_event_unref (event);
2306 case GST_EVENT_FLUSH_START:
2307 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2308 GST_INFO_OBJECT (overlay, "text flush start");
2309 overlay->text_flushing = TRUE;
2310 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2311 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2312 gst_event_unref (event);
2316 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2317 overlay->text_eos = TRUE;
2318 GST_INFO_OBJECT (overlay, "text EOS");
2319 /* wake up the video chain, it might be waiting for a text buffer or
2320 * a text segment update */
2321 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2322 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2323 gst_event_unref (event);
2327 ret = gst_pad_event_default (pad, parent, event);
2335 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
2338 gboolean ret = FALSE;
2339 GstBaseTextOverlay *overlay = NULL;
2341 overlay = GST_BASE_TEXT_OVERLAY (parent);
2343 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2345 switch (GST_EVENT_TYPE (event)) {
2346 case GST_EVENT_CAPS:
2350 gst_event_parse_caps (event, &caps);
2351 ret = gst_base_text_overlay_setcaps (overlay, caps);
2352 gst_event_unref (event);
2355 case GST_EVENT_SEGMENT:
2357 const GstSegment *segment;
2359 GST_DEBUG_OBJECT (overlay, "received new segment");
2361 gst_event_parse_segment (event, &segment);
2363 if (segment->format == GST_FORMAT_TIME) {
2364 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
2367 gst_segment_copy_into (segment, &overlay->segment);
2369 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2370 ("received non-TIME newsegment event on video input"));
2373 ret = gst_pad_event_default (pad, parent, event);
2377 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2378 GST_INFO_OBJECT (overlay, "video EOS");
2379 overlay->video_eos = TRUE;
2380 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2381 ret = gst_pad_event_default (pad, parent, event);
2383 case GST_EVENT_FLUSH_START:
2384 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2385 GST_INFO_OBJECT (overlay, "video flush start");
2386 overlay->video_flushing = TRUE;
2387 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2388 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2389 ret = gst_pad_event_default (pad, parent, event);
2391 case GST_EVENT_FLUSH_STOP:
2392 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2393 GST_INFO_OBJECT (overlay, "video flush stop");
2394 overlay->video_flushing = FALSE;
2395 overlay->video_eos = FALSE;
2396 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2397 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2398 ret = gst_pad_event_default (pad, parent, event);
2401 ret = gst_pad_event_default (pad, parent, event);
2409 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
2412 gboolean ret = FALSE;
2413 GstBaseTextOverlay *overlay;
2415 overlay = GST_BASE_TEXT_OVERLAY (parent);
2417 switch (GST_QUERY_TYPE (query)) {
2418 case GST_QUERY_CAPS:
2420 GstCaps *filter, *caps;
2422 gst_query_parse_caps (query, &filter);
2423 caps = gst_base_text_overlay_get_videosink_caps (pad, overlay, filter);
2424 gst_query_set_caps_result (query, caps);
2425 gst_caps_unref (caps);
2430 ret = gst_pad_query_default (pad, parent, query);
2437 /* Called with lock held */
2439 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2441 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2443 if (overlay->text_buffer) {
2444 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2445 overlay->text_buffer);
2446 gst_buffer_unref (overlay->text_buffer);
2447 overlay->text_buffer = NULL;
2450 /* Let the text task know we used that buffer */
2451 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2454 /* We receive text buffers here. If they are out of segment we just ignore them.
2455 If the buffer is in our segment we keep it internally except if another one
2456 is already waiting here, in that case we wait that it gets kicked out */
2457 static GstFlowReturn
2458 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
2461 GstFlowReturn ret = GST_FLOW_OK;
2462 GstBaseTextOverlay *overlay = NULL;
2463 gboolean in_seg = FALSE;
2464 guint64 clip_start = 0, clip_stop = 0;
2466 overlay = GST_BASE_TEXT_OVERLAY (parent);
2468 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2470 if (overlay->text_flushing) {
2471 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2472 ret = GST_FLOW_FLUSHING;
2473 GST_LOG_OBJECT (overlay, "text flushing");
2477 if (overlay->text_eos) {
2478 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2480 GST_LOG_OBJECT (overlay, "text EOS");
2484 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2485 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2486 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2487 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2488 GST_BUFFER_DURATION (buffer)));
2490 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2493 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2494 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2496 stop = GST_CLOCK_TIME_NONE;
2498 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2499 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2505 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2506 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2507 else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2508 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2510 /* Wait for the previous buffer to go away */
2511 while (overlay->text_buffer != NULL) {
2512 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2513 GST_DEBUG_PAD_NAME (pad));
2514 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2515 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2516 if (overlay->text_flushing) {
2517 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2518 ret = GST_FLOW_FLUSHING;
2523 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2524 overlay->text_segment.position = clip_start;
2526 overlay->text_buffer = buffer; /* pass ownership of @buffer */
2529 /* That's a new text buffer we need to render */
2530 overlay->need_render = TRUE;
2532 /* in case the video chain is waiting for a text buffer, wake it up */
2533 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2536 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2540 gst_buffer_unref (buffer);
2545 static GstFlowReturn
2546 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2549 GstBaseTextOverlayClass *klass;
2550 GstBaseTextOverlay *overlay;
2551 GstFlowReturn ret = GST_FLOW_OK;
2552 gboolean in_seg = FALSE;
2553 guint64 start, stop, clip_start = 0, clip_stop = 0;
2555 GstVideoOverlayCompositionMeta *composition_meta;
2557 overlay = GST_BASE_TEXT_OVERLAY (parent);
2559 composition_meta = gst_buffer_get_video_overlay_composition_meta (buffer);
2560 if (composition_meta) {
2561 if (overlay->upstream_composition != composition_meta->overlay) {
2562 GST_DEBUG ("GstVideoOverlayCompositionMeta found.");
2563 overlay->upstream_composition = composition_meta->overlay;
2564 overlay->need_render = TRUE;
2566 } else if (overlay->upstream_composition != NULL) {
2567 overlay->upstream_composition = NULL;
2568 overlay->need_render = TRUE;
2571 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2573 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2574 goto missing_timestamp;
2576 /* ignore buffers that are outside of the current segment */
2577 start = GST_BUFFER_TIMESTAMP (buffer);
2579 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2580 stop = GST_CLOCK_TIME_NONE;
2582 stop = start + GST_BUFFER_DURATION (buffer);
2585 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2586 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2587 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2589 /* segment_clip() will adjust start unconditionally to segment_start if
2590 * no stop time is provided, so handle this ourselves */
2591 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2592 goto out_of_segment;
2594 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2595 &clip_start, &clip_stop);
2598 goto out_of_segment;
2600 /* if the buffer is only partially in the segment, fix up stamps */
2601 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2602 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2603 buffer = gst_buffer_make_writable (buffer);
2604 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2606 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2609 /* now, after we've done the clipping, fix up end time if there's no
2610 * duration (we only use those estimated values internally though, we
2611 * don't want to set bogus values on the buffer itself) */
2613 if (overlay->info.fps_n && overlay->info.fps_d) {
2614 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2615 stop = start + gst_util_uint64_scale_int (GST_SECOND,
2616 overlay->info.fps_d, overlay->info.fps_n);
2618 GST_LOG_OBJECT (overlay, "no duration, assuming minimal duration");
2619 stop = start + 1; /* we need to assume some interval */
2623 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2627 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2629 if (overlay->video_flushing)
2632 if (overlay->video_eos)
2635 if (overlay->silent) {
2636 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2637 ret = gst_pad_push (overlay->srcpad, buffer);
2639 /* Update position */
2640 overlay->segment.position = clip_start;
2645 /* Text pad not linked, rendering internal text */
2646 if (!overlay->text_linked) {
2647 if (klass->get_text) {
2648 text = klass->get_text (overlay, buffer);
2650 text = g_strdup (overlay->default_text);
2653 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2654 "text: '%s'", GST_STR_NULL (text));
2656 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2658 if (text != NULL && *text != '\0') {
2659 /* Render and push */
2660 gst_base_text_overlay_render_text (overlay, text, -1);
2661 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2663 /* Invalid or empty string */
2664 ret = gst_pad_push (overlay->srcpad, buffer);
2667 /* Text pad linked, check if we have a text buffer queued */
2668 if (overlay->text_buffer) {
2669 gboolean pop_text = FALSE, valid_text_time = TRUE;
2670 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2671 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2672 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2673 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2674 GstClockTime vid_running_time, vid_running_time_end;
2676 /* if the text buffer isn't stamped right, pop it off the
2677 * queue and display it for the current video frame only */
2678 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2679 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2680 GST_WARNING_OBJECT (overlay,
2681 "Got text buffer with invalid timestamp or duration");
2683 valid_text_time = FALSE;
2685 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2686 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2690 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2692 vid_running_time_end =
2693 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2696 /* If timestamp and duration are valid */
2697 if (valid_text_time) {
2699 gst_segment_to_running_time (&overlay->text_segment,
2700 GST_FORMAT_TIME, text_start);
2701 text_running_time_end =
2702 gst_segment_to_running_time (&overlay->text_segment,
2703 GST_FORMAT_TIME, text_end);
2706 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2707 GST_TIME_ARGS (text_running_time),
2708 GST_TIME_ARGS (text_running_time_end));
2709 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2710 GST_TIME_ARGS (vid_running_time),
2711 GST_TIME_ARGS (vid_running_time_end));
2713 /* Text too old or in the future */
2714 if (valid_text_time && text_running_time_end <= vid_running_time) {
2715 /* text buffer too old, get rid of it and do nothing */
2716 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2718 gst_base_text_overlay_pop_text (overlay);
2719 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2720 goto wait_for_text_buf;
2721 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2722 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2723 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2724 /* Push the video frame */
2725 ret = gst_pad_push (overlay->srcpad, buffer);
2731 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2732 in_text = (gchar *) map.data;
2736 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2737 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2738 * here on purpose, this is something that needs fixing upstream */
2739 if (!g_utf8_validate (in_text, in_size, NULL)) {
2740 const gchar *end = NULL;
2742 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2743 in_text = g_strndup (in_text, in_size);
2744 while (!g_utf8_validate (in_text, in_size, &end) && end)
2745 *((gchar *) end) = '*';
2748 /* Get the string */
2749 if (overlay->have_pango_markup) {
2750 text = g_strndup (in_text, in_size);
2752 text = g_markup_escape_text (in_text, in_size);
2755 if (text != NULL && *text != '\0') {
2756 gint text_len = strlen (text);
2758 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2759 text[text_len - 1] == '\r')) {
2762 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2763 gst_base_text_overlay_render_text (overlay, text, text_len);
2765 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2766 gst_base_text_overlay_render_text (overlay, " ", 1);
2768 if (in_text != (gchar *) map.data)
2771 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2772 gst_base_text_overlay_render_text (overlay, " ", 1);
2775 gst_buffer_unmap (overlay->text_buffer, &map);
2777 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2778 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2780 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2781 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2786 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2787 gst_base_text_overlay_pop_text (overlay);
2788 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2791 gboolean wait_for_text_buf = TRUE;
2793 if (overlay->text_eos)
2794 wait_for_text_buf = FALSE;
2796 if (!overlay->wait_text)
2797 wait_for_text_buf = FALSE;
2799 /* Text pad linked, but no text buffer available - what now? */
2800 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2801 GstClockTime text_start_running_time, text_position_running_time;
2802 GstClockTime vid_running_time;
2805 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2806 GST_BUFFER_TIMESTAMP (buffer));
2807 text_start_running_time =
2808 gst_segment_to_running_time (&overlay->text_segment,
2809 GST_FORMAT_TIME, overlay->text_segment.start);
2810 text_position_running_time =
2811 gst_segment_to_running_time (&overlay->text_segment,
2812 GST_FORMAT_TIME, overlay->text_segment.position);
2814 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2815 vid_running_time < text_start_running_time) ||
2816 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2817 vid_running_time < text_position_running_time)) {
2818 wait_for_text_buf = FALSE;
2822 if (wait_for_text_buf) {
2823 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2824 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2825 GST_DEBUG_OBJECT (overlay, "resuming");
2826 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2827 goto wait_for_text_buf;
2829 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2830 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2831 ret = gst_pad_push (overlay->srcpad, buffer);
2838 /* Update position */
2839 overlay->segment.position = clip_start;
2845 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2846 gst_buffer_unref (buffer);
2852 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2853 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2854 gst_buffer_unref (buffer);
2855 return GST_FLOW_FLUSHING;
2859 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2860 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2861 gst_buffer_unref (buffer);
2862 return GST_FLOW_EOS;
2866 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2867 gst_buffer_unref (buffer);
2872 static GstStateChangeReturn
2873 gst_base_text_overlay_change_state (GstElement * element,
2874 GstStateChange transition)
2876 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2877 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2879 switch (transition) {
2880 case GST_STATE_CHANGE_PAUSED_TO_READY:
2881 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2882 overlay->text_flushing = TRUE;
2883 overlay->video_flushing = TRUE;
2884 /* pop_text will broadcast on the GCond and thus also make the video
2885 * chain exit if it's waiting for a text buffer */
2886 gst_base_text_overlay_pop_text (overlay);
2887 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2893 ret = parent_class->change_state (element, transition);
2894 if (ret == GST_STATE_CHANGE_FAILURE)
2897 switch (transition) {
2898 case GST_STATE_CHANGE_READY_TO_PAUSED:
2899 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2900 overlay->text_flushing = FALSE;
2901 overlay->video_flushing = FALSE;
2902 overlay->video_eos = FALSE;
2903 overlay->text_eos = FALSE;
2904 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2905 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2906 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2916 plugin_init (GstPlugin * plugin)
2918 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2919 GST_TYPE_TEXT_OVERLAY) ||
2920 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2921 GST_TYPE_TIME_OVERLAY) ||
2922 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2923 GST_TYPE_CLOCK_OVERLAY) ||
2924 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2925 GST_TYPE_TEXT_RENDER)) {
2929 /*texttestsrc_plugin_init(module, plugin); */
2931 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2936 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2937 pango, "Pango-based text rendering and overlay", plugin_init,
2938 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)