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.
26 #include <gst/video/video.h>
27 #include "gsttextoverlay.h"
32 * - calculating the position of the shading rectangle is
33 * not really right (try with text "L"), to say the least.
34 * Seems to work at least with latin script though.
35 * - check final x/y position and text width/height so that
36 * we don't do out-of-memory access when blitting the text.
37 * Also, we do not want to blit over the right or left margin.
38 * - what about text with newline characters? Cairo doesn't deal
39 * with that (we'd need to fix text_height usage for that as well)
40 * - upstream caps renegotiation, ie. when video window gets resized
43 GST_DEBUG_CATEGORY_EXTERN (cairo_debug);
44 #define GST_CAT_DEFAULT cairo_debug
46 static GstElementDetails cairo_text_overlay_details = {
48 "Filter/Editor/Video",
49 "Adds text strings on top of a video buffer",
50 "David Schleef <ds@schleef.org>"
67 #define DEFAULT_YPAD 25
68 #define DEFAULT_XPAD 25
69 #define DEFAULT_FONT "sans"
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,
120 static void gst_text_overlay_base_init (gpointer g_class)
122 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
124 gst_element_class_add_pad_template (element_class,
125 gst_static_pad_template_get (&cairo_text_overlay_src_template_factory));
126 gst_element_class_add_pad_template (element_class,
127 gst_static_pad_template_get (&video_sink_template_factory));
128 gst_element_class_add_pad_template (element_class,
129 gst_static_pad_template_get (&text_sink_template_factory));
131 gst_element_class_set_details (element_class, &cairo_text_overlay_details);
135 gst_text_overlay_class_init (GstCairoTextOverlayClass * klass)
137 GObjectClass *gobject_class;
138 GstElementClass *gstelement_class;
140 gobject_class = (GObjectClass *) klass;
141 gstelement_class = (GstElementClass *) klass;
143 gobject_class->finalize = gst_text_overlay_finalize;
144 gobject_class->set_property = gst_text_overlay_set_property;
146 gstelement_class->change_state =
147 GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
149 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
150 g_param_spec_string ("text", "text",
151 "Text to be display.", "", G_PARAM_WRITABLE));
152 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
153 g_param_spec_boolean ("shaded-background", "shaded background",
154 "Whether to shade the background under the text area", FALSE,
156 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
157 g_param_spec_string ("valign", "vertical alignment",
158 "Vertical alignment of the text. "
159 "Can be either 'baseline', 'bottom', or 'top'",
160 "baseline", G_PARAM_WRITABLE));
161 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
162 g_param_spec_string ("halign", "horizontal alignment",
163 "Horizontal alignment of the text. "
164 "Can be either 'left', 'right', or 'center'",
165 "center", G_PARAM_WRITABLE));
166 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
167 g_param_spec_int ("xpad", "horizontal paddding",
168 "Horizontal paddding when using left/right alignment",
169 G_MININT, G_MAXINT, DEFAULT_XPAD, G_PARAM_WRITABLE));
170 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
171 g_param_spec_int ("ypad", "vertical padding",
172 "Vertical padding when using top/bottom alignment",
173 G_MININT, G_MAXINT, DEFAULT_YPAD, G_PARAM_WRITABLE));
174 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
175 g_param_spec_int ("deltax", "X position modifier",
176 "Shift X position to the left or to the right. Unit is pixels.",
177 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
178 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY,
179 g_param_spec_int ("deltay", "Y position modifier",
180 "Shift Y position up or down. Unit is pixels.",
181 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
182 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
183 g_param_spec_string ("font-desc", "font description",
184 "Pango font description of font "
185 "to be used for rendering. "
186 "See documentation of "
187 "pango_font_description_from_string"
188 " for syntax.", "", G_PARAM_WRITABLE));
192 gst_text_overlay_finalize (GObject * object)
194 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
196 gst_collect_pads_stop (overlay->collect);
197 gst_object_unref (overlay->collect);
199 g_free (overlay->text_fill_image);
200 g_free (overlay->text_outline_image);
202 g_free (overlay->default_text);
203 g_free (overlay->font);
205 G_OBJECT_CLASS (parent_class)->finalize (object);
209 gst_text_overlay_init (GstCairoTextOverlay * overlay,
210 GstCairoTextOverlayClass * klass)
213 overlay->video_sinkpad =
214 gst_pad_new_from_template (gst_static_pad_template_get
215 (&video_sink_template_factory), "video_sink");
216 gst_pad_set_getcaps_function (overlay->video_sinkpad,
217 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
218 gst_pad_set_setcaps_function (overlay->video_sinkpad,
219 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
220 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
223 overlay->text_sinkpad =
224 gst_pad_new_from_template (gst_static_pad_template_get
225 (&text_sink_template_factory), "text_sink");
226 gst_pad_set_link_function (overlay->text_sinkpad,
227 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
228 gst_pad_set_unlink_function (overlay->text_sinkpad,
229 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
230 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
234 gst_pad_new_from_template (gst_static_pad_template_get
235 (&cairo_text_overlay_src_template_factory), "src");
236 gst_pad_set_getcaps_function (overlay->srcpad,
237 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
238 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
240 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
241 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
242 overlay->xpad = DEFAULT_XPAD;
243 overlay->ypad = DEFAULT_YPAD;
247 overlay->default_text = g_strdup ("");
248 overlay->need_render = TRUE;
250 overlay->font = g_strdup (DEFAULT_FONT);
251 overlay->slant = CAIRO_FONT_SLANT_NORMAL;
252 overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
254 gst_text_overlay_font_init (overlay);
259 overlay->collect = gst_collect_pads_new ();
261 gst_collect_pads_set_function (overlay->collect,
262 GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
264 overlay->video_collect_data = gst_collect_pads_add_pad (overlay->collect,
265 overlay->video_sinkpad, sizeof (GstCollectData));
267 /* text pad will be added when it is linked */
268 overlay->text_collect_data = NULL;
272 gst_text_overlay_font_init (GstCairoTextOverlay * overlay)
274 cairo_font_extents_t font_extents;
275 cairo_surface_t *surface;
278 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
279 cr = cairo_create (surface);
281 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
282 cairo_set_font_size (cr, overlay->scale);
284 cairo_font_extents (cr, &font_extents);
285 overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
286 overlay->need_render = TRUE;
289 cairo_surface_destroy (surface);
293 gst_text_overlay_set_property (GObject * object, guint prop_id,
294 const GValue * value, GParamSpec * pspec)
296 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (object);
298 GST_OBJECT_LOCK (overlay);
302 g_free (overlay->default_text);
303 overlay->default_text = g_value_dup_string (value);
307 overlay->want_shading = g_value_get_boolean (value);
311 const gchar *s = g_value_get_string (value);
313 if (strcasecmp (s, "baseline") == 0)
314 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE;
315 else if (strcasecmp (s, "bottom") == 0)
316 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM;
317 else if (strcasecmp (s, "top") == 0)
318 overlay->valign = GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP;
320 g_warning ("Invalid 'valign' property value: %s", s);
324 const gchar *s = g_value_get_string (value);
326 if (strcasecmp (s, "left") == 0)
327 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT;
328 else if (strcasecmp (s, "right") == 0)
329 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT;
330 else if (strcasecmp (s, "center") == 0)
331 overlay->halign = GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER;
333 g_warning ("Invalid 'halign' property value: %s", s);
337 overlay->xpad = g_value_get_int (value);
341 overlay->ypad = g_value_get_int (value);
345 overlay->deltax = g_value_get_int (value);
349 overlay->deltay = g_value_get_int (value);
353 g_free (overlay->font);
354 overlay->font = g_value_dup_string (value);
355 if (overlay->font == NULL)
356 overlay->font = g_strdup (DEFAULT_FONT);
357 gst_text_overlay_font_init (overlay);
361 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
366 overlay->need_render = TRUE;
368 GST_OBJECT_UNLOCK (overlay);
372 gst_text_overlay_render_text (GstCairoTextOverlay * overlay,
373 const gchar * text, gint textlen)
375 cairo_text_extents_t extents;
376 cairo_surface_t *surface;
382 textlen = strlen (text);
384 string = g_strndup (text, textlen);
386 if (overlay->need_render) {
387 GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
389 GST_DEBUG ("Using previously rendered text.");
390 g_return_if_fail (overlay->text_fill_image != NULL);
391 g_return_if_fail (overlay->text_outline_image != NULL);
395 overlay->text_fill_image =
396 g_realloc (overlay->text_fill_image,
397 4 * overlay->width * overlay->font_height);
399 surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
400 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
403 cr = cairo_create (surface);
405 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
406 cairo_set_font_size (cr, overlay->scale);
409 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
410 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
412 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
417 cairo_text_extents (cr, string, &extents);
418 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
420 switch (overlay->halign) {
421 case GST_CAIRO_TEXT_OVERLAY_HALIGN_LEFT:
424 case GST_CAIRO_TEXT_OVERLAY_HALIGN_CENTER:
425 x = (overlay->width - extents.width) / 2;
427 case GST_CAIRO_TEXT_OVERLAY_HALIGN_RIGHT:
428 x = overlay->width - extents.width - overlay->xpad;
433 x += overlay->deltax;
435 overlay->text_x0 = x;
436 overlay->text_x1 = x + extents.x_advance;
438 overlay->text_dy = (extents.height + extents.y_bearing);
439 y = overlay->font_height - overlay->text_dy;
441 cairo_move_to (cr, x, y);
442 cairo_show_text (cr, string);
446 cairo_surface_destroy (surface);
450 overlay->text_outline_image =
451 g_realloc (overlay->text_outline_image,
452 4 * overlay->width * overlay->font_height);
454 surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
455 CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
458 cr = cairo_create (surface);
460 cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
461 cairo_set_font_size (cr, overlay->scale);
464 cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
465 cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
466 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
471 cairo_move_to (cr, x, y);
472 cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
473 cairo_set_line_width (cr, 1.0);
474 cairo_text_path (cr, string);
481 cairo_surface_destroy (surface);
483 overlay->need_render = FALSE;
487 gst_text_overlay_getcaps (GstPad * pad)
489 GstCairoTextOverlay *overlay;
493 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
495 if (pad == overlay->srcpad)
496 otherpad = overlay->video_sinkpad;
498 otherpad = overlay->srcpad;
500 /* we can do what the peer can */
501 caps = gst_pad_peer_get_caps (otherpad);
504 const GstCaps *templ;
506 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
508 /* filtered against our padtemplate */
509 templ = gst_pad_get_pad_template_caps (otherpad);
510 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
511 temp = gst_caps_intersect (caps, templ);
512 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
513 gst_caps_unref (caps);
514 /* this is what we can do */
517 /* no peer, our padtemplate is enough then */
518 caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
521 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
523 gst_object_unref (overlay);
528 /* FIXME: upstream nego (e.g. when the video window is resized) */
531 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
533 GstCairoTextOverlay *overlay;
534 GstStructure *structure;
535 gboolean ret = FALSE;
538 if (!GST_PAD_IS_SINK (pad))
541 g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
543 overlay = GST_CAIRO_TEXT_OVERLAY (gst_pad_get_parent (pad));
547 structure = gst_caps_get_structure (caps, 0);
548 fps = gst_structure_get_value (structure, "framerate");
550 if (gst_structure_get_int (structure, "width", &overlay->width) &&
551 gst_structure_get_int (structure, "height", &overlay->height) &&
553 ret = gst_pad_set_caps (overlay->srcpad, caps);
556 overlay->fps_n = gst_value_get_fraction_numerator (fps);
557 overlay->fps_d = gst_value_get_fraction_denominator (fps);
559 gst_object_unref (overlay);
564 static GstPadLinkReturn
565 gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
567 GstCairoTextOverlay *overlay;
569 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
571 GST_DEBUG_OBJECT (overlay, "Text pad linked");
573 if (overlay->text_collect_data == NULL) {
574 overlay->text_collect_data = gst_collect_pads_add_pad (overlay->collect,
575 overlay->text_sinkpad, sizeof (GstCollectData));
578 overlay->need_render = TRUE;
580 return GST_PAD_LINK_OK;
584 gst_text_overlay_text_pad_unlinked (GstPad * pad)
586 GstCairoTextOverlay *overlay;
588 /* don't use gst_pad_get_parent() here, will deadlock */
589 overlay = GST_CAIRO_TEXT_OVERLAY (GST_PAD_PARENT (pad));
591 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
593 if (overlay->text_collect_data) {
594 gst_collect_pads_remove_pad (overlay->collect, overlay->text_sinkpad);
595 overlay->text_collect_data = NULL;
598 overlay->need_render = TRUE;
601 #define BOX_SHADING_VAL -80
606 gst_text_overlay_shade_y (GstCairoTextOverlay * overlay, guchar * dest,
607 guint dest_stride, gint y0, gint y1)
611 x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
612 x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
614 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
615 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
617 for (i = y0; i < y1; ++i) {
618 for (j = x0; j < x1; ++j) {
619 gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;
621 dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
627 gst_text_overlay_blit_1 (GstCairoTextOverlay * overlay, guchar * dest,
628 guchar * text_image, gint val, guint dest_stride)
636 for (i = 0; i < overlay->font_height; i++) {
637 for (j = 0; j < overlay->width; j++) {
638 x = dest[(i + y0) * dest_stride + j];
639 a = text_image[4 * (i * overlay->width + j) + 1];
640 dest[(i + y0) * dest_stride + j] = (y * a + x * (255 - a)) / 255;
646 gst_text_overlay_blit_sub2x2 (GstCairoTextOverlay * overlay, guchar * dest,
647 guchar * text_image, gint val, guint dest_stride)
655 for (i = 0; i < overlay->font_height; i += 2) {
656 for (j = 0; j < overlay->width; j += 2) {
657 x = dest[(i / 2 + y0) * dest_stride + j / 2];
658 a = (text_image[4 * (i * overlay->width + j) + 1] +
659 text_image[4 * (i * overlay->width + j + 1) + 1] +
660 text_image[4 * ((i + 1) * overlay->width + j) + 1] +
661 text_image[4 * ((i + 1) * overlay->width + j + 1) + 1] + 2) / 4;
662 dest[(i / 2 + y0) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
669 gst_text_overlay_push_frame (GstCairoTextOverlay * overlay,
670 GstBuffer * video_frame)
675 video_frame = gst_buffer_make_writable (video_frame);
677 switch (overlay->valign) {
678 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BOTTOM:
679 ypos = overlay->height - overlay->font_height - overlay->ypad;
681 case GST_CAIRO_TEXT_OVERLAY_VALIGN_BASELINE:
682 ypos = overlay->height - (overlay->font_height - overlay->text_dy)
685 case GST_CAIRO_TEXT_OVERLAY_VALIGN_TOP:
686 ypos = overlay->ypad;
689 ypos = overlay->ypad;
693 ypos += overlay->deltay;
695 y = GST_BUFFER_DATA (video_frame);
696 u = y + I420_U_OFFSET (overlay->width, overlay->height);
697 v = y + I420_V_OFFSET (overlay->width, overlay->height);
699 /* shaded background box */
700 if (overlay->want_shading) {
701 gst_text_overlay_shade_y (overlay,
702 y, I420_Y_ROWSTRIDE (overlay->width),
703 ypos + overlay->text_dy, ypos + overlay->font_height);
706 /* blit outline text on video image */
707 gst_text_overlay_blit_1 (overlay,
708 y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width),
709 overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width));
710 gst_text_overlay_blit_sub2x2 (overlay,
711 u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
712 overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width));
713 gst_text_overlay_blit_sub2x2 (overlay,
714 v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
715 overlay->text_outline_image, 128, I420_V_ROWSTRIDE (overlay->width));
717 /* blit text on video image */
718 gst_text_overlay_blit_1 (overlay,
719 y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width),
720 overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width));
721 gst_text_overlay_blit_sub2x2 (overlay,
722 u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
723 overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width));
724 gst_text_overlay_blit_sub2x2 (overlay,
725 v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
726 overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width));
728 return gst_pad_push (overlay->srcpad, video_frame);
732 gst_text_overlay_pop_video (GstCairoTextOverlay * overlay)
736 buf = gst_collect_pads_pop (overlay->collect, overlay->video_collect_data);
737 g_return_if_fail (buf != NULL);
738 gst_buffer_unref (buf);
742 gst_text_overlay_pop_text (GstCairoTextOverlay * overlay)
746 if (overlay->text_collect_data) {
747 buf = gst_collect_pads_pop (overlay->collect, overlay->text_collect_data);
748 g_return_if_fail (buf != NULL);
749 gst_buffer_unref (buf);
752 overlay->need_render = TRUE;
755 /* This function is called when there is data on all pads */
757 gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
759 GstCairoTextOverlay *overlay;
760 GstFlowReturn ret = GST_FLOW_OK;
761 GstClockTime now, txt_end, frame_end;
762 GstBuffer *video_frame = NULL;
763 GstBuffer *text_buf = NULL;
767 overlay = GST_CAIRO_TEXT_OVERLAY (data);
769 GST_DEBUG ("Collecting");
771 video_frame = gst_collect_pads_peek (overlay->collect,
772 overlay->video_collect_data);
774 /* send EOS if video stream EOSed regardless of text stream */
775 if (video_frame == NULL) {
776 GST_DEBUG ("Video stream at EOS");
777 if (overlay->text_collect_data) {
778 text_buf = gst_collect_pads_pop (overlay->collect,
779 overlay->text_collect_data);
781 gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
782 ret = GST_FLOW_UNEXPECTED;
786 if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
787 g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
790 now = GST_BUFFER_TIMESTAMP (video_frame);
792 if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
793 frame_end = now + GST_BUFFER_DURATION (video_frame);
794 } else if (overlay->fps_n > 0) {
795 frame_end = now + gst_util_uint64_scale_int (GST_SECOND,
796 overlay->fps_d, overlay->fps_n);
798 /* magic value, does not really matter since texts
799 * tend to span quite a few frames in practice anyway */
800 frame_end = now + GST_SECOND / 25;
803 GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
804 GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
806 /* text pad not linked? */
807 if (overlay->text_collect_data == NULL) {
808 GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
809 GST_STR_NULL (overlay->default_text));
810 if (overlay->default_text && *overlay->default_text != '\0') {
811 gst_text_overlay_render_text (overlay, overlay->default_text, -1);
812 ret = gst_text_overlay_push_frame (overlay, video_frame);
814 ret = gst_pad_push (overlay->srcpad, video_frame);
816 gst_text_overlay_pop_video (overlay);
821 text_buf = gst_collect_pads_peek (overlay->collect,
822 overlay->text_collect_data);
824 /* just push the video frame if the text stream has EOSed */
825 if (text_buf == NULL) {
826 GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
827 ret = gst_pad_push (overlay->srcpad, video_frame);
828 gst_text_overlay_pop_video (overlay);
833 /* if the text buffer isn't stamped right, pop it off the
834 * queue and display it for the current video frame only */
835 if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
836 GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
837 GST_WARNING ("Got text buffer with invalid time stamp or duration");
838 gst_text_overlay_pop_text (overlay);
839 GST_BUFFER_TIMESTAMP (text_buf) = now;
840 GST_BUFFER_DURATION (text_buf) = frame_end - now;
843 txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
845 GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
846 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
848 /* if the text buffer is too old, pop it off the
849 * queue and return so we get a new one next time */
851 GST_DEBUG ("Text buffer too old, popping off the queue");
852 gst_text_overlay_pop_text (overlay);
857 /* if the video frame ends before the text even starts,
858 * just push it out as is and pop it off the queue */
859 if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
860 GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
861 ret = gst_pad_push (overlay->srcpad, video_frame);
862 gst_text_overlay_pop_video (overlay);
867 /* text duration overlaps video frame duration */
868 text = g_strndup ((gchar *) GST_BUFFER_DATA (text_buf),
869 GST_BUFFER_SIZE (text_buf));
870 g_strdelimit (text, "\n\r\t", ' ');
871 text_len = strlen (text);
874 GST_DEBUG ("Rendering text '%*s'", text_len, text);;
875 gst_text_overlay_render_text (overlay, text, text_len);
877 GST_DEBUG ("No text to render (empty buffer)");
878 gst_text_overlay_render_text (overlay, " ", 1);
883 gst_text_overlay_pop_video (overlay);
884 ret = gst_text_overlay_push_frame (overlay, video_frame);
891 gst_buffer_unref (text_buf);
894 gst_buffer_unref (video_frame);
900 static GstStateChangeReturn
901 gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
903 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
904 GstCairoTextOverlay *overlay = GST_CAIRO_TEXT_OVERLAY (element);
906 switch (transition) {
907 case GST_STATE_CHANGE_READY_TO_PAUSED:
908 gst_collect_pads_start (overlay->collect);
910 case GST_STATE_CHANGE_PAUSED_TO_READY:
911 /* need to unblock the collectpads before calling the
912 * parent change_state so that streaming can finish */
913 gst_collect_pads_stop (overlay->collect);
919 ret = parent_class->change_state (element, transition);
920 if (ret == GST_STATE_CHANGE_FAILURE)
923 switch (transition) {