8 #include "gsttextoverlay.h"
9 /*#include "gsttexttestsrc.h"*/
10 /*#include "gstsubparse.h"*/
11 /*#include "SDL_blit.h"*/
13 static GstElementDetails textoverlay_details = {
15 "Filter/Editor/Video",
16 "Adds text strings on top of a video buffer",
17 "Gustavo J. A. M. Carneiro <gjc@inescporto.pt>"
31 GST_PAD_TEMPLATE_FACTORY(textoverlay_src_template_factory,
38 "format", GST_PROPS_LIST(
39 GST_PROPS_FOURCC(GST_STR_FOURCC("I420"))
41 "width", GST_PROPS_INT_RANGE(0, G_MAXINT),
42 "height", GST_PROPS_INT_RANGE(0, G_MAXINT)
46 GST_PAD_TEMPLATE_FACTORY(video_sink_template_factory,
53 "format", GST_PROPS_LIST(
54 GST_PROPS_FOURCC(GST_STR_FOURCC("I420"))
56 "width", GST_PROPS_INT_RANGE(0, G_MAXINT),
57 "height", GST_PROPS_INT_RANGE(0, G_MAXINT)
61 GST_PAD_TEMPLATE_FACTORY(text_sink_template_factory,
67 "text/x-pango-markup",
72 static void gst_textoverlay_base_init (gpointer g_class);
73 static void gst_textoverlay_class_init(GstTextOverlayClass *klass);
74 static void gst_textoverlay_init(GstTextOverlay *overlay);
75 static void gst_textoverlay_set_property(GObject *object,
79 static void gst_textoverlay_get_property(GObject *object,
83 static GstElementStateReturn gst_textoverlay_change_state(GstElement *element);
84 static void gst_textoverlay_finalize(GObject *object);
87 static GstElementClass *parent_class = NULL;
88 /*static guint gst_textoverlay_signals[LAST_SIGNAL] = { 0 }; */
92 gst_textoverlay_get_type(void)
94 static GType textoverlay_type = 0;
96 if (!textoverlay_type) {
97 static const GTypeInfo textoverlay_info = {
98 sizeof(GstTextOverlayClass),
99 gst_textoverlay_base_init,
101 (GClassInitFunc)gst_textoverlay_class_init,
104 sizeof(GstTextOverlay),
106 (GInstanceInitFunc)gst_textoverlay_init,
108 textoverlay_type = g_type_register_static(GST_TYPE_ELEMENT, "GstTextOverlay",
109 &textoverlay_info, 0);
111 return textoverlay_type;
115 gst_textoverlay_base_init (gpointer g_class)
117 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
119 gst_element_class_add_pad_template (element_class, GST_PAD_TEMPLATE_GET (textoverlay_src_template_factory));
120 gst_element_class_add_pad_template (element_class, GST_PAD_TEMPLATE_GET (video_sink_template_factory));
121 gst_element_class_add_pad_template (element_class, GST_PAD_TEMPLATE_GET (text_sink_template_factory));
123 gst_element_class_set_details (element_class, &textoverlay_details);
127 gst_textoverlay_class_init(GstTextOverlayClass *klass)
129 GObjectClass *gobject_class;
130 GstElementClass *gstelement_class;
132 gobject_class = (GObjectClass*)klass;
133 gstelement_class = (GstElementClass*)klass;
135 parent_class = g_type_class_peek_parent(klass);
137 gobject_class->finalize = gst_textoverlay_finalize;
138 gobject_class->set_property = gst_textoverlay_set_property;
139 gobject_class->get_property = gst_textoverlay_get_property;
141 gstelement_class->change_state = gst_textoverlay_change_state;
142 klass->pango_context = pango_ft2_get_context(72, 72);
143 g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_TEXT,
144 g_param_spec_string("text", "text",
145 "Text to be display,"
146 " in pango markup format.",
147 "", G_PARAM_WRITABLE));
148 g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_VALIGN,
149 g_param_spec_string("valign", "vertical alignment",
150 "Vertical alignment of the text. "
151 "Can be either 'baseline', 'bottom', or 'top'",
152 "baseline", G_PARAM_WRITABLE));
153 g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_HALIGN,
154 g_param_spec_string("halign", "horizontal alignment",
155 "Horizontal alignment of the text. "
156 "Can be either 'left', 'right', or 'center'",
157 "center", G_PARAM_WRITABLE));
158 g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_X0,
159 g_param_spec_int("x0", "X position",
160 "Initial X position."
161 " Horizontal aligment takes this point"
163 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
164 g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_Y0,
165 g_param_spec_int("y0", "Y position",
166 "Initial Y position."
167 " Vertical aligment takes this point"
169 G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
170 g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_FONT_DESC,
171 g_param_spec_string("font-desc", "font description",
172 "Pango font description of font "
173 "to be used for rendering. "
174 "See documentation of "
175 "pango_font_description_from_string"
177 "", G_PARAM_WRITABLE));
182 resize_bitmap(GstTextOverlay *overlay, int width, int height)
184 FT_Bitmap *bitmap = &overlay->bitmap;
185 int pitch = (width|3) + 1;
186 int size = pitch*height;
188 /* no need to keep reallocating; just keep the maximum size so far */
189 if (size <= overlay->bitmap_buffer_size) {
190 bitmap->rows = height;
191 bitmap->width = width;
192 bitmap->pitch = pitch;
193 memset(bitmap->buffer, 0, overlay->bitmap_buffer_size);
196 if (!bitmap->buffer) {
198 bitmap->pixel_mode = ft_pixel_mode_grays;
199 bitmap->num_grays = 256;
202 bitmap->buffer = g_realloc(bitmap->buffer, size);
204 bitmap->buffer = g_malloc(size);
205 bitmap->rows = height;
206 bitmap->width = width;
207 bitmap->pitch = pitch;
208 memset(bitmap->buffer, 0, size);
209 overlay->bitmap_buffer_size = size;
213 render_text(GstTextOverlay *overlay)
215 PangoRectangle ink_rect, logical_rect;
217 pango_layout_get_pixel_extents(overlay->layout, &ink_rect, &logical_rect);
218 resize_bitmap(overlay, ink_rect.width, ink_rect.height + ink_rect.y);
219 pango_ft2_render_layout(&overlay->bitmap, overlay->layout, 0, 0);
220 overlay->baseline_y = ink_rect.y;
223 /* static GstPadLinkReturn */
224 /* gst_textoverlay_text_sinkconnect (GstPad *pad, GstCaps *caps) */
226 /* return GST_PAD_LINK_DONE; */
230 static GstPadLinkReturn
231 gst_textoverlay_video_sinkconnect(GstPad *pad, GstCaps *caps)
233 GstTextOverlay *overlay;
235 overlay = GST_TEXTOVERLAY(gst_pad_get_parent(pad));
237 if (!GST_CAPS_IS_FIXED(caps))
238 return GST_PAD_LINK_DELAYED;
240 overlay->width = overlay->height = 0;
241 gst_caps_get_int(caps, "width", &overlay->width);
242 gst_caps_get_int(caps, "height", &overlay->height);
244 return gst_pad_try_set_caps(overlay->srcpad, caps);
249 gst_text_overlay_blit_yuv420(GstTextOverlay *overlay, FT_Bitmap *bitmap,
250 guchar *pixbuf, int x0, int y0)
252 int y; /* text bitmap coordinates */
253 int x1, y1; /* video buffer coordinates */
254 int rowinc, bit_rowinc, uv_rowinc;
255 guchar *p, *bitp, *u_p;
256 int video_width = overlay->width, video_height = overlay->height;
257 int bitmap_x0 = x0 < 1? -(x0 - 1) : 1; /* 1 pixel border */
258 int bitmap_y0 = y0 < 1? -(y0 - 1) : 1; /* 1 pixel border */
259 int bitmap_width = bitmap->width - bitmap_x0;
260 int bitmap_height = bitmap->rows - bitmap_y0;
265 if (x0 + bitmap_x0 + bitmap_width > video_width - 1) /* 1 pixel border */
266 bitmap_width -= x0 + bitmap_x0 + bitmap_width - video_width + 1;
267 if (y0 + bitmap_y0 + bitmap_height > video_height - 1) /* 1 pixel border */
268 bitmap_height -= y0 + bitmap_y0 + bitmap_height - video_height + 1;
270 rowinc = video_width - bitmap_width;
271 uv_rowinc = video_width / 2 - bitmap_width / 2;
272 bit_rowinc = bitmap->pitch - bitmap_width;
273 u_plane_size = (video_width / 2)*(video_height / 2);
277 p = pixbuf + video_width*y1 + x1;
278 bitp = bitmap->buffer + bitmap->pitch*bitmap_y0 + bitmap_x0;
279 for (y = bitmap_y0; y < bitmap_height; y++){
281 for(n=bitmap_width; n>0; --n){
284 p[-1] = CLAMP(p[-1] - v, 0, 255);
285 p[ 1] = CLAMP(p[ 1] - v, 0, 255);
286 p[-video_width] = CLAMP(p[-video_width] - v, 0, 255);
287 p[ video_width] = CLAMP(p[ video_width] - v, 0, 255);
299 bitp = bitmap->buffer + bitmap->pitch*bitmap_y0 + bitmap_x0;
300 p = pixbuf + video_width*y1 + x1;
301 u_p = pixbuf + video_width*video_height + (video_width >> 1)*(y1 >> 1) + (x1 >> 1);
305 for ( ; y < bitmap_height; y++){
309 for(n = bitmap_width; n>0; --n){
314 u_p[0] = u_p[u_plane_size] = 0x80;
324 /*if (!skip_x && !skip_y) u_p--; */
328 u_p += skip_y? uv_rowinc : 0;
334 gst_textoverlay_video_chain(GstPad *pad, GstData *_data)
336 GstBuffer *buf = GST_BUFFER (_data);
337 GstTextOverlay *overlay;
341 g_return_if_fail(pad != NULL);
342 g_return_if_fail(GST_IS_PAD(pad));
343 g_return_if_fail(buf != NULL);
344 overlay = GST_TEXTOVERLAY(gst_pad_get_parent(pad));
345 g_return_if_fail(overlay != NULL);
346 g_return_if_fail(GST_IS_TEXTOVERLAY(overlay));
348 pixbuf = GST_BUFFER_DATA(buf);
352 switch (overlay->valign)
354 case GST_TEXT_OVERLAY_VALIGN_BOTTOM:
355 y0 += overlay->bitmap.rows;
357 case GST_TEXT_OVERLAY_VALIGN_BASELINE:
358 y0 -= (overlay->bitmap.rows - overlay->baseline_y);
360 case GST_TEXT_OVERLAY_VALIGN_TOP:
364 switch (overlay->halign)
366 case GST_TEXT_OVERLAY_HALIGN_LEFT:
368 case GST_TEXT_OVERLAY_HALIGN_RIGHT:
369 x0 -= overlay->bitmap.width;
371 case GST_TEXT_OVERLAY_HALIGN_CENTER:
372 x0 -= overlay->bitmap.width / 2;
376 if (overlay->bitmap.buffer)
377 gst_text_overlay_blit_yuv420(overlay, &overlay->bitmap, pixbuf, x0, y0);
379 gst_pad_push(overlay->srcpad, GST_DATA (buf));
382 #define PAST_END(buffer, time) \
383 (GST_BUFFER_TIMESTAMP (buffer) != GST_CLOCK_TIME_NONE && \
384 GST_BUFFER_DURATION (buffer) != GST_CLOCK_TIME_NONE && \
385 GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) \
389 gst_textoverlay_loop(GstElement *element)
391 GstTextOverlay *overlay;
392 GstBuffer *video_frame;
395 g_return_if_fail(element != NULL);
396 g_return_if_fail(GST_IS_TEXTOVERLAY(element));
397 overlay = GST_TEXTOVERLAY(element);
399 video_frame = GST_BUFFER (gst_pad_pull(overlay->video_sinkpad));
400 now = GST_BUFFER_TIMESTAMP(video_frame);
403 * This state machine has a bug that can't be resolved easily.
404 * (Needs a more complicated state machine.) Basically, if the
405 * text that came from a buffer from the sink pad is being
406 * displayed, and the default text is changed by set_parameter,
407 * we'll incorrectly display the default text.
409 * Otherwise, this is a pretty decent state machine that handles
410 * buffer timestamps and durations correctly. (I think)
413 while (overlay->next_buffer == NULL){
414 GST_DEBUG("attempting to pull a buffer");
416 /* read all text buffers until we get one "in the future" */
417 if(!GST_PAD_IS_USABLE(overlay->text_sinkpad)){
420 overlay->next_buffer = GST_BUFFER (gst_pad_pull(overlay->text_sinkpad));
421 if (!overlay->next_buffer)
424 if (PAST_END(overlay->next_buffer, now)){
425 gst_buffer_unref(overlay->next_buffer);
426 overlay->next_buffer = NULL;
430 if (overlay->next_buffer &&
431 (GST_BUFFER_TIMESTAMP(overlay->next_buffer) <= now ||
432 GST_BUFFER_TIMESTAMP(overlay->next_buffer) == GST_CLOCK_TIME_NONE)){
433 GST_DEBUG("using new buffer");
435 if (overlay->current_buffer){
436 gst_buffer_unref (overlay->current_buffer);
438 overlay->current_buffer = overlay->next_buffer;
439 overlay->next_buffer = NULL;
441 GST_DEBUG ( "rendering '%*s'",
442 GST_BUFFER_SIZE(overlay->current_buffer),
443 GST_BUFFER_DATA(overlay->current_buffer));
444 pango_layout_set_markup(overlay->layout,
445 GST_BUFFER_DATA(overlay->current_buffer),
446 GST_BUFFER_SIZE(overlay->current_buffer));
447 render_text(overlay);
448 overlay->need_render = FALSE;
451 if (overlay->current_buffer && PAST_END(overlay->current_buffer, now)){
452 GST_DEBUG("dropping old buffer");
454 gst_buffer_unref(overlay->current_buffer);
455 overlay->current_buffer = NULL;
457 overlay->need_render = TRUE;
460 if(overlay->need_render){
461 GST_DEBUG ( "rendering '%s'", overlay->default_text);
462 pango_layout_set_markup(overlay->layout,
463 overlay->default_text, strlen(overlay->default_text));
464 render_text(overlay);
466 overlay->need_render = FALSE;
469 gst_textoverlay_video_chain(overlay->srcpad, GST_DATA (video_frame));
473 static GstElementStateReturn
474 gst_textoverlay_change_state(GstElement *element)
476 GstTextOverlay *overlay;
478 overlay = GST_TEXTOVERLAY(element);
480 switch (GST_STATE_TRANSITION(element))
482 case GST_STATE_PAUSED_TO_PLAYING:
484 case GST_STATE_PLAYING_TO_PAUSED:
486 case GST_STATE_PAUSED_TO_READY:
490 parent_class->change_state(element);
492 return GST_STATE_SUCCESS;
496 gst_textoverlay_finalize(GObject *object)
498 GstTextOverlay *overlay = GST_TEXTOVERLAY(object);
500 if (overlay->layout) {
501 g_object_unref(overlay->layout);
502 overlay->layout = NULL;
504 if (overlay->bitmap.buffer) {
505 g_free(overlay->bitmap.buffer);
506 overlay->bitmap.buffer = NULL;
509 G_OBJECT_CLASS(parent_class)->finalize(object);
513 gst_textoverlay_init(GstTextOverlay *overlay)
516 overlay->video_sinkpad = gst_pad_new_from_template(
517 GST_PAD_TEMPLATE_GET(video_sink_template_factory), "video_sink");
518 /* gst_pad_set_chain_function(overlay->video_sinkpad, gst_textoverlay_video_chain); */
519 gst_pad_set_link_function(overlay->video_sinkpad, gst_textoverlay_video_sinkconnect);
520 gst_element_add_pad(GST_ELEMENT(overlay), overlay->video_sinkpad);
523 overlay->text_sinkpad = gst_pad_new_from_template(
524 GST_PAD_TEMPLATE_GET(text_sink_template_factory), "text_sink");
525 /* gst_pad_set_link_function(overlay->text_sinkpad, gst_textoverlay_text_sinkconnect); */
526 gst_element_add_pad(GST_ELEMENT(overlay), overlay->text_sinkpad);
529 overlay->srcpad = gst_pad_new_from_template(
530 GST_PAD_TEMPLATE_GET(textoverlay_src_template_factory), "src");
531 gst_element_add_pad(GST_ELEMENT(overlay), overlay->srcpad);
533 overlay->layout = pango_layout_new(GST_TEXTOVERLAY_GET_CLASS(overlay)->pango_context);
534 memset(&overlay->bitmap, 0, sizeof(overlay->bitmap));
536 overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
537 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
538 overlay->x0 = overlay->y0 = 0;
540 overlay->default_text = g_strdup("");
541 overlay->need_render = TRUE;
543 gst_element_set_loop_function(GST_ELEMENT(overlay), gst_textoverlay_loop);
548 gst_textoverlay_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
550 GstTextOverlay *overlay;
552 /* it's not null if we got it, but it might not be ours */
553 g_return_if_fail(GST_IS_TEXTOVERLAY(object));
554 overlay = GST_TEXTOVERLAY(object);
560 if(overlay->default_text){
561 g_free(overlay->default_text);
563 overlay->default_text = g_strdup(g_value_get_string(value));
564 overlay->need_render = TRUE;
568 if (strcasecmp(g_value_get_string(value), "baseline") == 0)
569 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
570 else if (strcasecmp(g_value_get_string(value), "bottom") == 0)
571 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM;
572 else if (strcasecmp(g_value_get_string(value), "top") == 0)
573 overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP;
575 g_warning("Invalid 'valign' property value: %s",
576 g_value_get_string(value));
580 if (strcasecmp(g_value_get_string(value), "left") == 0)
581 overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT;
582 else if (strcasecmp(g_value_get_string(value), "right") == 0)
583 overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
584 else if (strcasecmp(g_value_get_string(value), "center") == 0)
585 overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
587 g_warning("Invalid 'halign' property value: %s",
588 g_value_get_string(value));
592 overlay->x0 = g_value_get_int(value);
596 overlay->y0 = g_value_get_int(value);
601 PangoFontDescription *desc;
602 desc = pango_font_description_from_string(g_value_get_string(value));
604 g_message("font description set: %s", g_value_get_string(value));
605 pango_layout_set_font_description(overlay->layout, desc);
606 pango_font_description_free(desc);
607 render_text(overlay);
609 g_warning("font description parse failed: %s", g_value_get_string(value));
619 gst_textoverlay_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
621 GstTextOverlay *overlay;
623 /* it's not null if we got it, but it might not be ours */
624 g_return_if_fail(GST_IS_TEXTOVERLAY(object));
625 overlay = GST_TEXTOVERLAY(object);
629 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
635 plugin_init(GstPlugin *plugin)
637 if (!gst_element_register (plugin, "textoverlay", GST_RANK_PRIMARY, GST_TYPE_TEXTOVERLAY))
640 /*texttestsrc_plugin_init(module, plugin);*/
641 /*subparse_plugin_init(module, plugin);*/