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.
25 #include <gst/video/video.h>
26 #include "gsttextoverlay.h"
31 * - calculating the position of the shading rectangle is
32 * not really right (try with text "L"), to say the least.
33 * Seems to work at least with latin script though.
34 * - check final x/y position and text width/height so that
35 * we don't do out-of-memory access when blitting the text.
36 * Also, we do not want to blit over the right or left margin.
37 * - what about text with newline characters? Cairo doesn't deal
38 * with that (we'd need to fix text_height usage for that as well)
39 * - upstream caps renegotiation, ie. when video window gets resized
42 GST_DEBUG_CATEGORY_EXTERN (cairo_debug);
43 #define GST_CAT_DEFAULT cairo_debug
45 static const GstElementDetails cairo_text_overlay_details =
46 GST_ELEMENT_DETAILS ("Text overlay",
47 "Filter/Editor/Video",
48 "Adds text strings on top of a video buffer",
49 "David Schleef <ds@schleef.org>");
65 #define DEFAULT_YPAD 25
66 #define DEFAULT_XPAD 25
67 #define DEFAULT_FONT "sans"
69 #define GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE 20.0
71 static GstStaticPadTemplate cairo_text_overlay_src_template_factory =
72 GST_STATIC_PAD_TEMPLATE ("src",
75 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
78 static GstStaticPadTemplate video_sink_template_factory =
79 GST_STATIC_PAD_TEMPLATE ("video_sink",
82 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
85 static GstStaticPadTemplate text_sink_template_factory =
86 GST_STATIC_PAD_TEMPLATE ("text_sink",
89 GST_STATIC_CAPS ("text/plain")
92 static void gst_text_overlay_set_property (GObject * object,
93 guint prop_id, const GValue * value, GParamSpec * pspec);
94 static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element,
95 GstStateChange transition);
96 static GstCaps *gst_text_overlay_getcaps (GstPad * pad);
97 static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
98 static GstPadLinkReturn gst_text_overlay_text_pad_linked (GstPad * pad,
100 static void gst_text_overlay_text_pad_unlinked (GstPad * pad);
101 static GstFlowReturn gst_text_overlay_collected (GstCollectPads * pads,
103 static void gst_text_overlay_finalize (GObject * object);
104 static void gst_text_overlay_font_init (GstCairoTextOverlay * overlay);
106 /* These macros are adapted from videotestsrc.c */
107 #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
108 #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
109 #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
111 #define I420_Y_OFFSET(w,h) (0)
112 #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
113 #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
115 #define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
117 GST_BOILERPLATE (GstCairoTextOverlay, gst_text_overlay, GstElement,
121 gst_text_overlay_base_init (gpointer g_class)
123 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
125 gst_element_class_add_pad_template (element_class,
126 gst_static_pad_template_get (&cairo_text_overlay_src_template_factory));
127 gst_element_class_add_pad_template (element_class,
128 gst_static_pad_template_get (&video_sink_template_factory));
129 gst_element_class_add_pad_template (element_class,
130 gst_static_pad_template_get (&text_sink_template_factory));
132 gst_element_class_set_details (element_class, &cairo_text_overlay_details);
136 gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
138 GObjectClass *gobject_class;
139 GstElementClass *gstelement_class;
141 gobject_class = (GObjectClass *) klass;
142 gstelement_class = (GstElementClass *) klass;
144 gobject_class->finalize = gst_text_overlay_finalize;
145 gobject_class->set_property = gst_text_overlay_set_property;
147 gstelement_class->change_state =
148 GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
150 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
151 g_param_spec_string ("text", "text",
152 "Text to be display.", "", G_PARAM_WRITABLE));
153 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
154 g_param_spec_boolean ("shaded-background", "shaded background",
155 "Whether to shade the background under the text area", FALSE,
157 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
158 g_param_spec_string ("valign", "vertical alignment",
159 "Vertical alignment of the text. "
160 "Can be either 'baseline', 'bottom', or 'top'",
161 "baseline", G_PARAM_WRITABLE));
162 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
163 g_param_spec_string ("halign", "horizontal alignment",
164 "Horizontal alignment of the text. "
165 "Can be either 'left', 'right', or 'center'",
166 "center", G_PARAM_WRITABLE));
167 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
168 g_param_spec_int ("xpad", "horizontal paddding",
169 "Horizontal paddding when using left/right alignment",
170 G_MININT, G_MAXINT, DEFAULT_XPAD, G_PARAM_WRITABLE));
171 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
172 g_param_spec_int ("ypad", "vertical padding",
173 "Vertical padding when using top/bottom alignment",
174 G_MININT, G_MAXINT, DEFAULT_YPAD, G_PARAM_WRITABLE));
175 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
176 g_param_spec_int ("deltax", "X position modifier",
177 "Shift X position to the left or to the right. Unit is pixels.",
178 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
179 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY,
180 g_param_spec_int ("deltay", "Y position modifier",
181 "Shift Y position up or down. Unit is pixels.",
182 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
183 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
184 g_param_spec_string ("font-desc", "font description",
185 "Pango font description of font "
186 "to be used for rendering. "
187 "See documentation of "
188 "pango_font_description_from_string"
189 " for syntax.", "", G_PARAM_WRITABLE));
193 gst_text_overlay_finalize (GObject * object)
195 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
197 gst_collect_pads_stop (overlay->collect);
198 gst_object_unref (overlay->collect);
200 g_free (overlay->text_fill_image);
201 g_free (overlay->text_outline_image);
203 g_free (overlay->default_text);
204 g_free (overlay->font);
206 G_OBJECT_CLASS (parent_class)->finalize (object);
210 gst_text_overlay_init (GstCairoTextOverlay * overlay,
211 GstCairoTextOverlayClass * klass)
214 overlay->video_sinkpad =
215 gst_pad_new_from_static_template (&video_sink_template_factory,
217 gst_pad_set_getcaps_function (overlay->video_sinkpad,
218 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
219 gst_pad_set_setcaps_function (overlay->video_sinkpad,
220 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
221 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
224 overlay->text_sinkpad =
225 gst_pad_new_from_static_template (&text_sink_template_factory,
227 gst_pad_set_link_function (overlay->text_sinkpad,
228 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
229 gst_pad_set_unlink_function (overlay->text_sinkpad,
230 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
231 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
235 gst_pad_new_from_static_template
236 (&cairo_text_overlay_src_template_factory, "src");
237 gst_pad_set_getcaps_function (overlay->srcpad,
238 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
239 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
241 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
242 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
243 overlay->xpad = DEFAULT_XPAD;
244 overlay->ypad = DEFAULT_YPAD;
248 overlay->default_text = g_strdup ("");
249 overlay->need_render = TRUE;
251 overlay->font = g_strdup (DEFAULT_FONT);
252 gst_text_overlay_font_init (overlay);
257 overlay->collect = gst_collect_pads_new ();
259 gst_collect_pads_set_function (overlay->collect,
260 GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
262 overlay->video_collect_data = gst_collect_pads_add_pad (overlay->collect,
263 overlay->video_sinkpad, sizeof (GstCollectData));
265 /* text pad will be added when it is linked */
266 overlay->text_collect_data = NULL;
270 gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
272 cairo_font_extents_t font_extents;
273 cairo_surface_t *surface;
275 gchar *font_desc, *sep;
277 font_desc = g_ascii_strdown (overlay->font, -1);
279 /* cairo_select_font_face() does not parse the size at the end, so we have
280 * to do that ourselves; same for slate and weight */
281 sep = MAX (strrchr (font_desc, ' '), strrchr (font_desc, ','));
282 if (sep != NULL && g_strtod (sep, NULL) > 0.0) {
283 /* there may be a suffix such as 'px', but we just ignore that for now */
284 overlay->scale = g_strtod (sep, NULL);
286 overlay->scale = GST_CAIRO_TEXT_OVERLAY_DEFAULT_SCALE;
288 if (strstr (font_desc, "bold"))
289 overlay->weight = CAIRO_FONT_WEIGHT_BOLD;
291 overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
293 if (strstr (font_desc, "italic"))
294 overlay->slant = CAIRO_FONT_SLANT_ITALIC;
295 else if (strstr (font_desc, "oblique"))
296 overlay->slant = CAIRO_FONT_SLANT_OBLIQUE;
298 overlay->slant = CAIRO_FONT_SLANT_NORMAL;
300 GST_LOG_OBJECT (overlay, "Font desc: '%s', scale=%f, weight=%d, slant=%d",
301 overlay->font, overlay->scale, overlay->weight, overlay->slant);
303 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
304 cr = cairo_create (surface);
306 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
307 cairo_set_font_size (cr, overlay->scale);
309 /* this has a static leak:
310 * http://lists.freedesktop.org/archives/cairo/2007-May/010623.html
312 cairo_font_extents (cr, &font_extents);
313 overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
314 overlay->need_render = TRUE;
317 cairo_surface_destroy (surface);
322 gst_text_overlay_set_property (GObject * object, guint prop_id,
323 const GValue * value, GParamSpec * pspec)
325 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
327 GST_OBJECT_LOCK (overlay);
331 g_free (overlay->default_text);
332 overlay->default_text = g_value_dup_string (value);
336 overlay->want_shading = g_value_get_boolean (value);
340 const gchar *s = g_value_get_string (value);
342 if (g_ascii_strcasecmp (s, "baseline") == 0)
343 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
344 else if (g_ascii_strcasecmp (s, "bottom") == 0)
345 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM;
346 else if (g_ascii_strcasecmp (s, "top") == 0)
347 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP;
349 g_warning ("Invalid 'valign' property value: %s", s);
353 const gchar *s = g_value_get_string (value);
355 if (g_ascii_strcasecmp (s, "left") == 0)
356 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT;
357 else if (g_ascii_strcasecmp (s, "right") == 0)
358 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT;
359 else if (g_ascii_strcasecmp (s, "center") == 0)
360 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
362 g_warning ("Invalid 'halign' property value: %s", s);
366 overlay->xpad = g_value_get_int (value);
370 overlay->ypad = g_value_get_int (value);
374 overlay->deltax = g_value_get_int (value);
378 overlay->deltay = g_value_get_int (value);
382 g_free (overlay->font);
383 overlay->font = g_value_dup_string (value);
384 if (overlay->font == NULL)
385 overlay->font = g_strdup (DEFAULT_FONT);
386 gst_text_overlay_font_init (overlay);
390 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
395 overlay->need_render = TRUE;
397 GST_OBJECT_UNLOCK (overlay);
401 gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
402 const gchar * text, gint textlen)
404 cairo_text_extents_t extents;
405 cairo_surface_t *surface;
411 textlen = strlen (text);
413 string = g_strndup (text, textlen);
415 if (overlay->need_render) {
416 GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
418 GST_DEBUG ("Using previously rendered text.");
419 g_return_if_fail (overlay->text_fill_image != NULL);
420 g_return_if_fail (overlay->text_outline_image != NULL);
424 overlay->text_fill_image =
425 g_realloc (overlay->text_fill_image,
426 4 * overlay->width * overlay->font_height);
428 surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
429 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
432 cr = cairo_create (surface);
434 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
435 cairo_set_font_size (cr, overlay->scale);
438 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
439 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
441 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
446 cairo_text_extents (cr, string, &extents);
447 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
449 switch (overlay->halign) {
450 case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
453 case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
454 x = (overlay->width - extents.width) / 2;
456 case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
457 x = overlay->width - extents.width - overlay->xpad;
462 x += overlay->deltax;
464 overlay->text_x0 = x;
465 overlay->text_x1 = x + extents.x_advance;
467 overlay->text_dy = (extents.height + extents.y_bearing);
468 y = overlay->font_height - overlay->text_dy;
470 cairo_move_to (cr, x, y);
471 cairo_show_text (cr, string);
475 cairo_surface_destroy (surface);
479 overlay->text_outline_image =
480 g_realloc (overlay->text_outline_image,
481 4 * overlay->width * overlay->font_height);
483 surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
484 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
487 cr = cairo_create (surface);
489 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
490 cairo_set_font_size (cr, overlay->scale);
493 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
494 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
495 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
500 cairo_move_to (cr, x, y);
501 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
502 cairo_set_line_width (cr, 1.0);
503 cairo_text_path (cr, string);
510 cairo_surface_destroy (surface);
512 overlay->need_render = FALSE;
516 gst_text_overlay_getcaps (GstPad * pad)
518 GstCairoTextOverlay *overlay;
522 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
524 if (pad == overlay->srcpad)
525 otherpad = overlay->video_sinkpad;
527 otherpad = overlay->srcpad;
529 /* we can do what the peer can */
530 caps = gst_pad_peer_get_caps (otherpad);
533 const GstCaps *templ;
535 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
537 /* filtered against our padtemplate */
538 templ = gst_pad_get_pad_template_caps (otherpad);
539 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
540 temp = gst_caps_intersect (caps, templ);
541 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
542 gst_caps_unref (caps);
543 /* this is what we can do */
546 /* no peer, our padtemplate is enough then */
547 caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
550 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
552 gst_object_unref (overlay);
557 /* FIXME: upstream nego (e.g. when the video window is resized) */
560 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
562 GstCairoTextOverlay *overlay;
563 GstStructure *structure;
564 gboolean ret = FALSE;
567 if (!GST_PAD_IS_SINK (pad))
570 g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
572 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
576 structure = gst_caps_get_structure (caps, 0);
577 fps = gst_structure_get_value (structure, "framerate");
579 if (gst_structure_get_int (structure, "width", &overlay->width) &&
580 gst_structure_get_int (structure, "height", &overlay->height) &&
582 ret = gst_pad_set_caps (overlay->srcpad, caps);
585 overlay->fps_n = gst_value_get_fraction_numerator (fps);
586 overlay->fps_d = gst_value_get_fraction_denominator (fps);
588 gst_object_unref (overlay);
593 static GstPadLinkReturn
594 gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
596 GstCairoTextOverlay *overlay;
598 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
600 GST_DEBUG_OBJECT (overlay, "Text pad linked");
602 if (overlay->text_collect_data == NULL) {
603 overlay->text_collect_data = gst_collect_pads_add_pad (overlay->collect,
604 overlay->text_sinkpad, sizeof (GstCollectData));
607 overlay->need_render = TRUE;
609 return GST_PAD_LINK_OK;
613 gst_text_overlay_text_pad_unlinked (GstPad * pad)
615 GstCairoTextOverlay *overlay;
617 /* don't use gst_pad_get_parent() here, will deadlock */
618 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
620 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
622 if (overlay->text_collect_data) {
623 gst_collect_pads_remove_pad (overlay->collect, overlay->text_sinkpad);
624 overlay->text_collect_data = NULL;
627 overlay->need_render = TRUE;
630 #define BOX_SHADING_VAL -80
635 gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
636 guint dest_stride, gint y0, gint y1)
640 x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
641 x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
643 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
644 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
646 for (i = y0; i < y1; ++i) {
647 for (j = x0; j < x1; ++j) {
648 gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;
650 dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
656 gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
657 guchar * text_image, gint val, guint dest_stride)
665 for (i = 0; i < overlay->font_height; i++) {
666 for (j = 0; j < overlay->width; j++) {
667 x = dest[(i + y0) * dest_stride + j];
668 a = text_image[4 * (i * overlay->width + j) + 1];
669 dest[(i + y0) * dest_stride + j] = (y * a + x * (255 - a)) / 255;
675 gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
676 guchar * text_image, gint val, guint dest_stride)
684 for (i = 0; i < overlay->font_height; i += 2) {
685 for (j = 0; j < overlay->width; j += 2) {
686 x = dest[(i / 2 + y0) * dest_stride + j / 2];
687 a = (text_image[4 * (i * overlay->width + j) + 1] +
688 text_image[4 * (i * overlay->width + j + 1) + 1] +
689 text_image[4 * ((i + 1) * overlay->width + j) + 1] +
690 text_image[4 * ((i + 1) * overlay->width + j + 1) + 1] + 2) / 4;
691 dest[(i / 2 + y0) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
698 gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
699 GstBuffer * video_frame)
704 video_frame = gst_buffer_make_writable (video_frame);
706 switch (overlay->valign) {
707 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
708 ypos = overlay->height - overlay->font_height - overlay->ypad;
710 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
711 ypos = overlay->height - (overlay->font_height - overlay->text_dy)
714 case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
715 ypos = overlay->ypad;
718 ypos = overlay->ypad;
722 ypos += overlay->deltay;
724 y = GST_BUFFER_DATA (video_frame);
725 u = y + I420_U_OFFSET (overlay->width, overlay->height);
726 v = y + I420_V_OFFSET (overlay->width, overlay->height);
728 /* shaded background box */
729 if (overlay->want_shading) {
730 gst_text_overlay_shade_y (overlay,
731 y, I420_Y_ROWSTRIDE (overlay->width),
732 ypos + overlay->text_dy, ypos + overlay->font_height);
735 /* blit outline text on video image */
736 gst_text_overlay_blit_1 (overlay,
737 y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width),
738 overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width));
739 gst_text_overlay_blit_sub2x2 (overlay,
740 u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
741 overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width));
742 gst_text_overlay_blit_sub2x2 (overlay,
743 v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
744 overlay->text_outline_image, 128, I420_V_ROWSTRIDE (overlay->width));
746 /* blit text on video image */
747 gst_text_overlay_blit_1 (overlay,
748 y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width),
749 overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width));
750 gst_text_overlay_blit_sub2x2 (overlay,
751 u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
752 overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width));
753 gst_text_overlay_blit_sub2x2 (overlay,
754 v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
755 overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width));
757 return gst_pad_push (overlay->srcpad, video_frame);
761 gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
765 buf = gst_collect_pads_pop (overlay->collect, overlay->video_collect_data);
766 g_return_if_fail (buf != NULL);
767 gst_buffer_unref (buf);
771 gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
775 if (overlay->text_collect_data) {
776 buf = gst_collect_pads_pop (overlay->collect, overlay->text_collect_data);
777 g_return_if_fail (buf != NULL);
778 gst_buffer_unref (buf);
781 overlay->need_render = TRUE;
784 /* This function is called when there is data on all pads */
786 gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
788 GstCairoTextOverlay *overlay;
789 GstFlowReturn ret = GST_FLOW_OK;
790 GstClockTime now, txt_end, frame_end;
791 GstBuffer *video_frame = NULL;
792 GstBuffer *text_buf = NULL;
796 overlay = GST_CAIRO_TEXT_OVERLAY (data);
798 GST_DEBUG ("Collecting");
800 video_frame = gst_collect_pads_peek (overlay->collect,
801 overlay->video_collect_data);
803 /* send EOS if video stream EOSed regardless of text stream */
804 if (video_frame == NULL) {
805 GST_DEBUG ("Video stream at EOS");
806 if (overlay->text_collect_data) {
807 text_buf = gst_collect_pads_pop (overlay->collect,
808 overlay->text_collect_data);
810 gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
811 ret = GST_FLOW_UNEXPECTED;
815 if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
816 g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
819 now = GST_BUFFER_TIMESTAMP (video_frame);
821 if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
822 frame_end = now + GST_BUFFER_DURATION (video_frame);
823 } else if (overlay->fps_n > 0) {
824 frame_end = now + gst_util_uint64_scale_int (GST_SECOND,
825 overlay->fps_d, overlay->fps_n);
827 /* magic value, does not really matter since texts
828 * tend to span quite a few frames in practice anyway */
829 frame_end = now + GST_SECOND / 25;
832 GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
833 GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
835 /* text pad not linked? */
836 if (overlay->text_collect_data == NULL) {
837 GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
838 GST_STR_NULL (overlay->default_text));
839 if (overlay->default_text && *overlay->default_text != '\0') {
840 gst_text_overlay_render_text (overlay, overlay->default_text, -1);
841 ret = gst_text_overlay_push_frame (overlay, video_frame);
843 ret = gst_pad_push (overlay->srcpad, video_frame);
845 gst_text_overlay_pop_video (overlay);
850 text_buf = gst_collect_pads_peek (overlay->collect,
851 overlay->text_collect_data);
853 /* just push the video frame if the text stream has EOSed */
854 if (text_buf == NULL) {
855 GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
856 ret = gst_pad_push (overlay->srcpad, video_frame);
857 gst_text_overlay_pop_video (overlay);
862 /* if the text buffer isn't stamped right, pop it off the
863 * queue and display it for the current video frame only */
864 if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
865 GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
866 GST_WARNING ("Got text buffer with invalid time stamp or duration");
867 gst_text_overlay_pop_text (overlay);
868 GST_BUFFER_TIMESTAMP (text_buf) = now;
869 GST_BUFFER_DURATION (text_buf) = frame_end - now;
872 txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
874 GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
875 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
877 /* if the text buffer is too old, pop it off the
878 * queue and return so we get a new one next time */
880 GST_DEBUG ("Text buffer too old, popping off the queue");
881 gst_text_overlay_pop_text (overlay);
886 /* if the video frame ends before the text even starts,
887 * just push it out as is and pop it off the queue */
888 if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
889 GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
890 ret = gst_pad_push (overlay->srcpad, video_frame);
891 gst_text_overlay_pop_video (overlay);
896 /* text duration overlaps video frame duration */
897 text = g_strndup ((gchar *) GST_BUFFER_DATA (text_buf),
898 GST_BUFFER_SIZE (text_buf));
899 g_strdelimit (text, "\n\r\t", ' ');
900 text_len = strlen (text);
903 GST_DEBUG ("Rendering text '%*s'", text_len, text);;
904 gst_text_overlay_render_text (overlay, text, text_len);
906 GST_DEBUG ("No text to render (empty buffer)");
907 gst_text_overlay_render_text (overlay, " ", 1);
912 gst_text_overlay_pop_video (overlay);
913 ret = gst_text_overlay_push_frame (overlay, video_frame);
920 gst_buffer_unref (text_buf);
923 gst_buffer_unref (video_frame);
929 static GstStateChangeReturn
930 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
932 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
933 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
935 switch (transition) {
936 case GST_STATE_CHANGE_READY_TO_PAUSED:
937 gst_collect_pads_start (overlay->collect);
939 case GST_STATE_CHANGE_PAUSED_TO_READY:
940 /* need to unblock the collectpads before calling the
941 * parent change_state so that streaming can finish */
942 gst_collect_pads_stop (overlay->collect);
948 ret = parent_class->change_state (element, transition);
949 if (ret == GST_STATE_CHANGE_FAILURE)
952 switch (transition) {