2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) <2003> David Schleef <ds@schleef.org>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
21 * SECTION:element-cairotextoverlay
23 * cairotextoverlay renders the text on top of the video frames.
26 * <title>Example launch line</title>
28 * gst-launch videotestsrc ! cairotextoverlay text="hello" ! autovideosink
37 #include <gst/video/video.h>
38 #include "gsttextoverlay.h"
43 * - calculating the position of the shading rectangle is
44 * not really right (try with text "L"), to say the least.
45 * Seems to work at least with latin script though.
46 * - check final x/y position and text width/height so that
47 * we don't do out-of-memory access when blitting the text.
48 * Also, we do not want to blit over the right or left margin.
49 * - what about text with newline characters? Cairo doesn't deal
50 * with that (we'd need to fix text_height usage for that as well)
51 * - upstream caps renegotiation, ie. when video window gets resized
54 GST_DEBUG_CATEGORY_EXTERN (cairo_debug);
55 #define GST_CAT_DEFAULT cairo_debug
72 #define DEFAULT_YPAD 25
73 #define DEFAULT_XPAD 25
74 #define DEFAULT_FONT "sans"
75 #define DEFAULT_SILENT FALSE
77 #define GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE 20.0
79 static GstStaticPadTemplate cairo_text_overlay_src_template_factory =
80 GST_STATIC_PAD_TEMPLATE ("src",
83 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
86 static GstStaticPadTemplate video_sink_template_factory =
87 GST_STATIC_PAD_TEMPLATE ("video_sink",
90 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
93 static GstStaticPadTemplate text_sink_template_factory =
94 GST_STATIC_PAD_TEMPLATE ("text_sink",
97 GST_STATIC_CAPS ("text/plain")
100 static void gst_text_overlay_set_property (GObject * object,
101 guint prop_id, const GValue * value, GParamSpec * pspec);
102 static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element,
103 GstStateChange transition);
104 static GstCaps *gst_text_overlay_getcaps (GstPad * pad);
105 static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
106 static GstPadLinkReturn gst_text_overlay_text_pad_linked (GstPad * pad,
108 static void gst_text_overlay_text_pad_unlinked (GstPad * pad);
109 static GstFlowReturn gst_text_overlay_collected (GstCollectPads * pads,
111 static void gst_text_overlay_finalize (GObject * object);
112 static void gst_text_overlay_font_init (GstCairoTextOverlay * overlay);
113 static gboolean gst_text_overlay_src_event (GstPad * pad, GstEvent * event);
114 static gboolean gst_text_overlay_video_event (GstPad * pad, GstEvent * event);
116 /* These macros are adapted from videotestsrc.c */
117 #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
118 #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
119 #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
121 #define I420_Y_OFFSET(w,h) (0)
122 #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
123 #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
125 #define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
127 GST_BOILERPLATE (GstCairoTextOverlay, gst_text_overlay, GstElement,
131 gst_text_overlay_base_init (gpointer g_class)
133 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
135 gst_element_class_add_static_pad_template (element_class,
136 &cairo_text_overlay_src_template_factory);
137 gst_element_class_add_static_pad_template (element_class,
138 &video_sink_template_factory);
139 gst_element_class_add_static_pad_template (element_class,
140 &text_sink_template_factory);
142 gst_element_class_set_details_simple (element_class, "Text overlay",
143 "Filter/Editor/Video",
144 "Adds text strings on top of a video buffer",
145 "David Schleef <ds@schleef.org>");
149 gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
151 GObjectClass *gobject_class;
152 GstElementClass *gstelement_class;
154 gobject_class = (GObjectClass *) klass;
155 gstelement_class = (GstElementClass *) klass;
157 gobject_class->finalize = gst_text_overlay_finalize;
158 gobject_class->set_property = gst_text_overlay_set_property;
160 gstelement_class->change_state =
161 GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
163 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
164 g_param_spec_string ("text", "text",
165 "Text to be display.", "",
166 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
167 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
168 g_param_spec_boolean ("shaded-background", "shaded background",
169 "Whether to shade the background under the text area", FALSE,
170 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
171 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
172 g_param_spec_string ("valign", "vertical alignment",
173 "Vertical alignment of the text. "
174 "Can be either 'baseline', 'bottom', or 'top'",
175 "baseline", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
176 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
177 g_param_spec_string ("halign", "horizontal alignment",
178 "Horizontal alignment of the text. "
179 "Can be either 'left', 'right', or 'center'",
180 "center", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
181 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
182 g_param_spec_int ("xpad", "horizontal paddding",
183 "Horizontal paddding when using left/right alignment",
184 G_MININT, G_MAXINT, DEFAULT_XPAD,
185 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
186 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
187 g_param_spec_int ("ypad", "vertical padding",
188 "Vertical padding when using top/bottom alignment",
189 G_MININT, G_MAXINT, DEFAULT_YPAD,
190 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
191 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
192 g_param_spec_int ("deltax", "X position modifier",
193 "Shift X position to the left or to the right. Unit is pixels.",
194 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
195 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY,
196 g_param_spec_int ("deltay", "Y position modifier",
197 "Shift Y position up or down. Unit is pixels.",
198 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
199 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
200 g_param_spec_string ("font-desc", "font description",
201 "Pango font description of font "
202 "to be used for rendering. "
203 "See documentation of "
204 "pango_font_description_from_string"
205 " for syntax.", "", G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
206 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
207 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SILENT,
208 g_param_spec_boolean ("silent", "silent",
209 "Whether to render the text string",
210 DEFAULT_SILENT, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
214 gst_text_overlay_finalize (GObject * object)
216 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
218 gst_collect_pads_stop (overlay->collect);
219 gst_object_unref (overlay->collect);
221 g_free (overlay->text_fill_image);
222 g_free (overlay->text_outline_image);
224 g_free (overlay->default_text);
225 g_free (overlay->font);
227 G_OBJECT_CLASS (parent_class)->finalize (object);
231 gst_text_overlay_init (GstCairoTextOverlay * overlay,
232 GstCairoTextOverlayClass * klass)
235 overlay->video_sinkpad =
236 gst_pad_new_from_static_template (&video_sink_template_factory,
238 gst_pad_set_getcaps_function (overlay->video_sinkpad,
239 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
240 gst_pad_set_setcaps_function (overlay->video_sinkpad,
241 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
242 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
245 overlay->text_sinkpad =
246 gst_pad_new_from_static_template (&text_sink_template_factory,
248 gst_pad_set_link_function (overlay->text_sinkpad,
249 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
250 gst_pad_set_unlink_function (overlay->text_sinkpad,
251 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
252 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
256 gst_pad_new_from_static_template
257 (&cairo_text_overlay_src_template_factory, "src");
258 gst_pad_set_getcaps_function (overlay->srcpad,
259 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
260 gst_pad_set_event_function (overlay->srcpad,
261 GST_DEBUG_FUNCPTR (gst_text_overlay_src_event));
262 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
264 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
265 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
266 overlay->xpad = DEFAULT_XPAD;
267 overlay->ypad = DEFAULT_YPAD;
271 overlay->default_text = g_strdup ("");
272 overlay->need_render = TRUE;
274 overlay->font = g_strdup (DEFAULT_FONT);
275 gst_text_overlay_font_init (overlay);
277 overlay->silent = DEFAULT_SILENT;
282 overlay->collect = gst_collect_pads_new ();
284 gst_collect_pads_set_function (overlay->collect,
285 GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
287 overlay->video_collect_data = gst_collect_pads_add_pad (overlay->collect,
288 overlay->video_sinkpad, sizeof (GstCollectData));
290 /* FIXME: hacked way to override/extend the event function of
291 * GstCollectPads; because it sets its own event function giving the
292 * element no access to events. Nicked from avimux. */
293 overlay->collect_event =
294 (GstPadEventFunction) GST_PAD_EVENTFUNC (overlay->video_sinkpad);
295 gst_pad_set_event_function (overlay->video_sinkpad,
296 GST_DEBUG_FUNCPTR (gst_text_overlay_video_event));
298 /* text pad will be added when it is linked */
299 overlay->text_collect_data = NULL;
303 gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
305 cairo_font_extents_t font_extents;
306 cairo_surface_t *surface;
308 gchar *font_desc, *sep;
310 font_desc = g_ascii_strdown (overlay->font, -1);
312 /* cairo_select_font_face() does not parse the size at the end, so we have
313 * to do that ourselves; same for slate and weight */
314 sep = MAX (strrchr (font_desc, ' '), strrchr (font_desc, ','));
315 if (sep != NULL && g_strtod (sep, NULL) > 0.0) {
316 /* there may be a suffix such as 'px', but we just ignore that for now */
317 overlay->scale = g_strtod (sep, NULL);
319 overlay->scale = GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE;
321 if (strstr (font_desc, "bold"))
322 overlay->weight = CAIRO_FONT_WEIGHT_BOLD;
324 overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
326 if (strstr (font_desc, "italic"))
327 overlay->slant = CAIRO_FONT_SLANT_ITALIC;
328 else if (strstr (font_desc, "oblique"))
329 overlay->slant = CAIRO_FONT_SLANT_OBLIQUE;
331 overlay->slant = CAIRO_FONT_SLANT_NORMAL;
333 GST_LOG_OBJECT (overlay, "Font desc: '%s', scale=%f, weight=%d, slant=%d",
334 overlay->font, overlay->scale, overlay->weight, overlay->slant);
336 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
337 cr = cairo_create (surface);
339 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
340 cairo_set_font_size (cr, overlay->scale);
342 /* this has a static leak:
343 * http://lists.freedesktop.org/archives/cairo/2007-May/010623.html
345 cairo_font_extents (cr, &font_extents);
346 overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
347 overlay->need_render = TRUE;
350 cairo_surface_destroy (surface);
355 gst_text_overlay_set_property (GObject * object, guint prop_id,
356 const GValue * value, GParamSpec * pspec)
358 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
360 GST_OBJECT_LOCK (overlay);
364 g_free (overlay->default_text);
365 overlay->default_text = g_value_dup_string (value);
369 overlay->want_shading = g_value_get_boolean (value);
373 const gchar *s = g_value_get_string (value);
375 if (g_ascii_strcasecmp (s, "baseline") == 0)
376 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
377 else if (g_ascii_strcasecmp (s, "bottom") == 0)
378 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM;
379 else if (g_ascii_strcasecmp (s, "top") == 0)
380 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP;
382 g_warning ("Invalid 'valign' property value: %s", s);
386 const gchar *s = g_value_get_string (value);
388 if (g_ascii_strcasecmp (s, "left") == 0)
389 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT;
390 else if (g_ascii_strcasecmp (s, "right") == 0)
391 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT;
392 else if (g_ascii_strcasecmp (s, "center") == 0)
393 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
395 g_warning ("Invalid 'halign' property value: %s", s);
399 overlay->xpad = g_value_get_int (value);
403 overlay->ypad = g_value_get_int (value);
407 overlay->deltax = g_value_get_int (value);
411 overlay->deltay = g_value_get_int (value);
415 g_free (overlay->font);
416 overlay->font = g_value_dup_string (value);
417 if (overlay->font == NULL)
418 overlay->font = g_strdup (DEFAULT_FONT);
419 gst_text_overlay_font_init (overlay);
423 overlay->silent = g_value_get_boolean (value);
426 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
431 overlay->need_render = TRUE;
433 GST_OBJECT_UNLOCK (overlay);
437 gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
438 const gchar * text, gint textlen)
440 cairo_text_extents_t extents;
441 cairo_surface_t *surface;
446 if (overlay->silent) {
447 GST_DEBUG_OBJECT (overlay, "Silent mode, not rendering");
452 textlen = strlen (text);
454 if (!overlay->need_render) {
455 GST_DEBUG ("Using previously rendered text.");
456 g_return_if_fail (overlay->text_fill_image != NULL);
457 g_return_if_fail (overlay->text_outline_image != NULL);
461 string = g_strndup (text, textlen);
462 GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
464 overlay->text_fill_image =
465 g_realloc (overlay->text_fill_image,
466 4 * overlay->width * overlay->font_height);
468 surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
469 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
472 cr = cairo_create (surface);
474 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
475 cairo_set_font_size (cr, overlay->scale);
478 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
479 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
481 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
486 cairo_text_extents (cr, string, &extents);
487 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
489 switch (overlay->halign) {
490 case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
493 case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
494 x = (overlay->width - extents.width) / 2;
496 case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
497 x = overlay->width - extents.width - overlay->xpad;
502 x += overlay->deltax;
504 overlay->text_x0 = x;
505 overlay->text_x1 = x + extents.x_advance;
507 overlay->text_dy = (extents.height + extents.y_bearing);
508 y = overlay->font_height - overlay->text_dy;
510 cairo_move_to (cr, x, y);
511 cairo_show_text (cr, string);
515 cairo_surface_destroy (surface);
519 overlay->text_outline_image =
520 g_realloc (overlay->text_outline_image,
521 4 * overlay->width * overlay->font_height);
523 surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
524 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
527 cr = cairo_create (surface);
529 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
530 cairo_set_font_size (cr, overlay->scale);
533 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
534 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
535 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
540 cairo_move_to (cr, x, y);
541 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
542 cairo_set_line_width (cr, 1.0);
543 cairo_text_path (cr, string);
550 cairo_surface_destroy (surface);
552 overlay->need_render = FALSE;
556 gst_text_overlay_getcaps (GstPad * pad)
558 GstCairoTextOverlay *overlay;
562 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
564 if (pad == overlay->srcpad)
565 otherpad = overlay->video_sinkpad;
567 otherpad = overlay->srcpad;
569 /* we can do what the peer can */
570 caps = gst_pad_peer_get_caps (otherpad);
573 const GstCaps *templ;
575 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
577 /* filtered against our padtemplate */
578 templ = gst_pad_get_pad_template_caps (otherpad);
579 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
580 temp = gst_caps_intersect (caps, templ);
581 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
582 gst_caps_unref (caps);
583 /* this is what we can do */
586 /* no peer, our padtemplate is enough then */
587 caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
590 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
592 gst_object_unref (overlay);
597 /* FIXME: upstream nego (e.g. when the video window is resized) */
600 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
602 GstCairoTextOverlay *overlay;
603 GstStructure *structure;
604 gboolean ret = FALSE;
607 if (!GST_PAD_IS_SINK (pad))
610 g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
612 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
616 structure = gst_caps_get_structure (caps, 0);
617 fps = gst_structure_get_value (structure, "framerate");
619 if (gst_structure_get_int (structure, "width", &overlay->width) &&
620 gst_structure_get_int (structure, "height", &overlay->height) &&
622 ret = gst_pad_set_caps (overlay->srcpad, caps);
625 overlay->fps_n = gst_value_get_fraction_numerator (fps);
626 overlay->fps_d = gst_value_get_fraction_denominator (fps);
628 gst_object_unref (overlay);
633 static GstPadLinkReturn
634 gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
636 GstCairoTextOverlay *overlay;
638 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
640 GST_DEBUG_OBJECT (overlay, "Text pad linked");
642 if (overlay->text_collect_data == NULL) {
643 overlay->text_collect_data = gst_collect_pads_add_pad (overlay->collect,
644 overlay->text_sinkpad, sizeof (GstCollectData));
647 overlay->need_render = TRUE;
649 return GST_PAD_LINK_OK;
653 gst_text_overlay_text_pad_unlinked (GstPad * pad)
655 GstCairoTextOverlay *overlay;
657 /* don't use gst_pad_get_parent() here, will deadlock */
658 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
660 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
662 if (overlay->text_collect_data) {
663 gst_collect_pads_remove_pad (overlay->collect, overlay->text_sinkpad);
664 overlay->text_collect_data = NULL;
667 overlay->need_render = TRUE;
670 #define BOX_SHADING_VAL -80
675 gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
676 guint dest_stride, gint y0, gint y1)
680 x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
681 x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
683 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
684 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
686 for (i = y0; i < y1; ++i) {
687 for (j = x0; j < x1; ++j) {
688 gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;
690 dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
696 gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
697 guchar * text_image, gint val, guint dest_stride, gint y0)
704 y0 = MIN (y0, overlay->height);
705 y1 = MIN (y0 + overlay->font_height, overlay->height);
707 for (i = y0; i < y1; i++) {
708 for (j = 0; j < overlay->width; j++) {
709 x = dest[i * dest_stride + j];
710 a = text_image[4 * ((i - y0) * overlay->width + j) + 1];
711 dest[i * dest_stride + j] = (y * a + x * (255 - a)) / 255;
717 gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
718 guchar * text_image, gint val, guint dest_stride, gint y0)
724 y0 = MIN (y0, overlay->height);
725 y1 = MIN (y0 + overlay->font_height, overlay->height);
729 for (i = y0; i < y1; i += 2) {
730 for (j = 0; j < overlay->width; j += 2) {
731 x = dest[(i / 2) * dest_stride + j / 2];
732 a = (text_image[4 * ((i - y0) * overlay->width + j) + 1] +
733 text_image[4 * ((i - y0) * overlay->width + j + 1) + 1] +
734 text_image[4 * ((i - y0 + 1) * overlay->width + j) + 1] +
735 text_image[4 * ((i - y0 + 1) * overlay->width + j + 1) + 1] + 2) / 4;
736 dest[(i / 2) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
743 gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
744 GstBuffer * video_frame)
749 video_frame = gst_buffer_make_writable (video_frame);
751 switch (overlay->valign) {
752 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
753 ypos = overlay->height - overlay->font_height - overlay->ypad;
755 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
756 ypos = overlay->height - (overlay->font_height - overlay->text_dy)
759 case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
760 ypos = overlay->ypad;
763 ypos = overlay->ypad;
767 ypos += overlay->deltay;
769 y = GST_BUFFER_DATA (video_frame);
770 u = y + I420_U_OFFSET (overlay->width, overlay->height);
771 v = y + I420_V_OFFSET (overlay->width, overlay->height);
773 /* shaded background box */
774 if (overlay->want_shading) {
775 gst_text_overlay_shade_y (overlay,
776 y, I420_Y_ROWSTRIDE (overlay->width),
777 ypos + overlay->text_dy, ypos + overlay->font_height);
780 /* blit outline text on video image */
781 gst_text_overlay_blit_1 (overlay,
783 overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width), ypos);
784 gst_text_overlay_blit_sub2x2 (overlay,
786 overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width),
788 gst_text_overlay_blit_sub2x2 (overlay, v, overlay->text_outline_image, 128,
789 I420_V_ROWSTRIDE (overlay->width), ypos);
791 /* blit text on video image */
792 gst_text_overlay_blit_1 (overlay,
794 overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width), ypos);
795 gst_text_overlay_blit_sub2x2 (overlay,
797 overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width), ypos);
798 gst_text_overlay_blit_sub2x2 (overlay,
800 overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width), ypos);
802 return gst_pad_push (overlay->srcpad, video_frame);
806 gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
810 buf = gst_collect_pads_pop (overlay->collect, overlay->video_collect_data);
811 g_return_if_fail (buf != NULL);
812 gst_buffer_unref (buf);
816 gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
820 if (overlay->text_collect_data) {
821 buf = gst_collect_pads_pop (overlay->collect, overlay->text_collect_data);
822 g_return_if_fail (buf != NULL);
823 gst_buffer_unref (buf);
826 overlay->need_render = TRUE;
829 /* This function is called when there is data on all pads */
831 gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
833 GstCairoTextOverlay *overlay;
834 GstFlowReturn ret = GST_FLOW_OK;
835 GstClockTime now, txt_end, frame_end;
836 GstBuffer *video_frame = NULL;
837 GstBuffer *text_buf = NULL;
841 overlay = GST_CAIRO_TEXT_OVERLAY (data);
843 GST_DEBUG ("Collecting");
845 video_frame = gst_collect_pads_peek (overlay->collect,
846 overlay->video_collect_data);
848 /* send EOS if video stream EOSed regardless of text stream */
849 if (video_frame == NULL) {
850 GST_DEBUG ("Video stream at EOS");
851 if (overlay->text_collect_data) {
852 text_buf = gst_collect_pads_pop (overlay->collect,
853 overlay->text_collect_data);
855 gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
856 ret = GST_FLOW_UNEXPECTED;
860 if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
861 g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
864 now = GST_BUFFER_TIMESTAMP (video_frame);
866 if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
867 frame_end = now + GST_BUFFER_DURATION (video_frame);
868 } else if (overlay->fps_n > 0) {
869 frame_end = now + gst_util_uint64_scale_int (GST_SECOND,
870 overlay->fps_d, overlay->fps_n);
872 /* magic value, does not really matter since texts
873 * tend to span quite a few frames in practice anyway */
874 frame_end = now + GST_SECOND / 25;
877 GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
878 GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
880 /* text pad not linked? */
881 if (overlay->text_collect_data == NULL) {
882 GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
883 GST_STR_NULL (overlay->default_text));
884 if (overlay->default_text && *overlay->default_text != '\0') {
885 gst_text_overlay_render_text (overlay, overlay->default_text, -1);
886 ret = gst_text_overlay_push_frame (overlay, video_frame);
888 ret = gst_pad_push (overlay->srcpad, video_frame);
890 gst_text_overlay_pop_video (overlay);
895 text_buf = gst_collect_pads_peek (overlay->collect,
896 overlay->text_collect_data);
898 /* just push the video frame if the text stream has EOSed */
899 if (text_buf == NULL) {
900 GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
901 ret = gst_pad_push (overlay->srcpad, video_frame);
902 gst_text_overlay_pop_video (overlay);
907 /* if the text buffer isn't stamped right, pop it off the
908 * queue and display it for the current video frame only */
909 if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
910 GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
911 GST_WARNING ("Got text buffer with invalid time stamp or duration");
912 gst_text_overlay_pop_text (overlay);
913 GST_BUFFER_TIMESTAMP (text_buf) = now;
914 GST_BUFFER_DURATION (text_buf) = frame_end - now;
917 txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
919 GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
920 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
922 /* if the text buffer is too old, pop it off the
923 * queue and return so we get a new one next time */
925 GST_DEBUG ("Text buffer too old, popping off the queue");
926 gst_text_overlay_pop_text (overlay);
931 /* if the video frame ends before the text even starts,
932 * just push it out as is and pop it off the queue */
933 if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
934 GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
935 ret = gst_pad_push (overlay->srcpad, video_frame);
936 gst_text_overlay_pop_video (overlay);
941 /* text duration overlaps video frame duration */
942 text = g_strndup ((gchar *) GST_BUFFER_DATA (text_buf),
943 GST_BUFFER_SIZE (text_buf));
944 g_strdelimit (text, "\n\r\t", ' ');
945 text_len = strlen (text);
948 GST_DEBUG ("Rendering text '%*s'", text_len, text);;
949 gst_text_overlay_render_text (overlay, text, text_len);
951 GST_DEBUG ("No text to render (empty buffer)");
952 gst_text_overlay_render_text (overlay, " ", 1);
957 gst_text_overlay_pop_video (overlay);
958 ret = gst_text_overlay_push_frame (overlay, video_frame);
965 gst_buffer_unref (text_buf);
968 gst_buffer_unref (video_frame);
975 gst_text_overlay_src_event (GstPad * pad, GstEvent * event)
977 GstCairoTextOverlay *overlay =
978 GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
981 /* forward events to the video sink, and, if it is linked, the text sink */
982 if (overlay->text_collect_data) {
983 gst_event_ref (event);
984 ret &= gst_pad_push_event (overlay->text_sinkpad, event);
986 ret &= gst_pad_push_event (overlay->video_sinkpad, event);
988 gst_object_unref (overlay);
993 gst_text_overlay_video_event (GstPad * pad, GstEvent * event)
995 gboolean ret = FALSE;
996 GstCairoTextOverlay *overlay = NULL;
998 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
1000 if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) {
1001 GST_DEBUG_OBJECT (overlay,
1002 "received new segment on video sink pad, forwarding");
1003 gst_event_ref (event);
1004 gst_pad_push_event (overlay->srcpad, event);
1007 /* now GstCollectPads can take care of the rest, e.g. EOS */
1008 ret = overlay->collect_event (pad, event);
1009 gst_object_unref (overlay);
1013 static GstStateChangeReturn
1014 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
1016 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1017 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
1019 switch (transition) {
1020 case GST_STATE_CHANGE_READY_TO_PAUSED:
1021 gst_collect_pads_start (overlay->collect);
1023 case GST_STATE_CHANGE_PAUSED_TO_READY:
1024 /* need to unblock the collectpads before calling the
1025 * parent change_state so that streaming can finish */
1026 gst_collect_pads_stop (overlay->collect);
1032 ret = parent_class->change_state (element, transition);
1033 if (ret == GST_STATE_CHANGE_FAILURE)
1036 switch (transition) {