summaryrefslogtreecommitdiffstats
path: root/ext/cairo/gsttextoverlay.c
diff options
context:
space:
mode:
authorTim-Philipp Müller <tim@centricular.net>2005-11-07 15:09:54 +0000
committerTim-Philipp Müller <tim@centricular.net>2005-11-07 15:09:54 +0000
commit8af7381d5cac93a8af28a303533f34de7aade137 (patch)
tree536e1510dfc741197d86233f11bf7efba2849c7d /ext/cairo/gsttextoverlay.c
parentfb1c3904f701a967840cf59cd0808034cfb70d7a (diff)
ext/cairo/: Port cairo textoverlay plugin to 0.9. Add 'shaded-background' property and redo position. Doesn't handle ...
Original commit message from CVS: * ext/cairo/Makefile.am: * ext/cairo/gstcairo.c: (plugin_init): * ext/cairo/gsttextoverlay.c: (gst_text_overlay_base_init), (gst_text_overlay_class_init), (gst_text_overlay_finalize), (gst_text_overlay_init), (gst_text_overlay_font_init), (gst_text_overlay_set_property), (gst_text_overlay_render_text), (gst_text_overlay_getcaps), (gst_text_overlay_setcaps), (gst_text_overlay_text_pad_linked), (gst_text_overlay_text_pad_unlinked), (gst_text_overlay_shade_y), (gst_text_overlay_blit_1), (gst_text_overlay_blit_sub2x2), (gst_text_overlay_push_frame), (gst_text_overlay_pop_video), (gst_text_overlay_pop_text), (gst_text_overlay_collected), (gst_text_overlay_change_state): * ext/cairo/gsttextoverlay.h: Port cairo textoverlay plugin to 0.9. Add 'shaded-background' property and redo position. Doesn't handle upstream renegotiation yet though.
Diffstat (limited to 'ext/cairo/gsttextoverlay.c')
-rw-r--r--ext/cairo/gsttextoverlay.c1112
1 files changed, 653 insertions, 459 deletions
diff --git a/ext/cairo/gsttextoverlay.c b/ext/cairo/gsttextoverlay.c
index 304ee5af..81d79df6 100644
--- a/ext/cairo/gsttextoverlay.c
+++ b/ext/cairo/gsttextoverlay.c
@@ -23,9 +23,26 @@
#endif
#include <string.h>
#include <strings.h>
-#include <gst/gst.h>
+#include <gst/video/video.h>
#include "gsttextoverlay.h"
+#include <cairo.h>
+
+/* FIXME:
+ * - calculating the position of the shading rectangle is
+ * not really right (try with text "L"), to say the least.
+ * Seems to work at least with latin script though.
+ * - check final x/y position and text width/height so that
+ * we don't do out-of-memory access when blitting the text.
+ * Also, we do not want to blit over the right or left margin.
+ * - what about text with newline characters? Cairo doesn't deal
+ * with that (we'd need to fix text_height usage for that as well)
+ * - upstream caps renegotiation, ie. when video window gets resized
+ */
+
+GST_DEBUG_CATEGORY_EXTERN (cairo_debug);
+#define GST_CAT_DEFAULT cairo_debug
+
static GstElementDetails textoverlay_details = {
"Text Overlay",
"Filter/Editor/Video",
@@ -37,30 +54,32 @@ enum
{
ARG_0,
ARG_TEXT,
+ ARG_SHADING,
ARG_VALIGN,
ARG_HALIGN,
- ARG_X0,
- ARG_Y0,
+ ARG_XPAD,
+ ARG_YPAD,
+ ARG_DELTAX,
+ ARG_DELTAY,
ARG_FONT_DESC
};
+#define DEFAULT_YPAD 25
+#define DEFAULT_XPAD 25
+#define DEFAULT_FONT "sans"
static GstStaticPadTemplate textoverlay_src_template_factory =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
- GST_STATIC_CAPS ("video/x-raw-yuv, "
- "format = (fourcc) I420, "
- "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
);
static GstStaticPadTemplate video_sink_template_factory =
GST_STATIC_PAD_TEMPLATE ("video_sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
- GST_STATIC_CAPS ("video/x-raw-yuv, "
- "format = (fourcc) I420, "
- "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
);
static GstStaticPadTemplate text_sink_template_factory =
@@ -70,50 +89,34 @@ GST_STATIC_PAD_TEMPLATE ("text_sink",
GST_STATIC_CAPS ("text/plain")
);
-static void gst_textoverlay_base_init (gpointer g_class);
-static void gst_textoverlay_class_init (GstTextOverlayClass * klass);
-static void gst_textoverlay_init (GstTextOverlay * overlay);
-static void gst_textoverlay_set_property (GObject * object,
+static void gst_text_overlay_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
-static void gst_textoverlay_get_property (GObject * object,
- guint prop_id, GValue * value, GParamSpec * pspec);
-static GstStateChangeReturn gst_textoverlay_change_state (GstElement * element,
+static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element,
GstStateChange transition);
-static void gst_textoverlay_finalize (GObject * object);
-
-
-static GstElementClass *parent_class = NULL;
-
-/*static guint gst_textoverlay_signals[LAST_SIGNAL] = { 0 }; */
-
-
-GType
-gst_textoverlay_get_type (void)
-{
- static GType textoverlay_type = 0;
-
- if (!textoverlay_type) {
- static const GTypeInfo textoverlay_info = {
- sizeof (GstTextOverlayClass),
- gst_textoverlay_base_init,
- NULL,
- (GClassInitFunc) gst_textoverlay_class_init,
- NULL,
- NULL,
- sizeof (GstTextOverlay),
- 0,
- (GInstanceInitFunc) gst_textoverlay_init,
- };
-
- textoverlay_type =
- g_type_register_static (GST_TYPE_ELEMENT, "GstTextOverlay",
- &textoverlay_info, 0);
- }
- return textoverlay_type;
-}
-
-static void
-gst_textoverlay_base_init (gpointer g_class)
+static GstCaps *gst_text_overlay_getcaps (GstPad * pad);
+static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps);
+static GstPadLinkReturn gst_text_overlay_text_pad_linked (GstPad * pad,
+ GstPad * peer);
+static void gst_text_overlay_text_pad_unlinked (GstPad * pad);
+static GstFlowReturn gst_text_overlay_collected (GstCollectPads * pads,
+ gpointer data);
+static void gst_text_overlay_finalize (GObject * object);
+static void gst_text_overlay_font_init (GstTextOverlay * overlay);
+
+/* These macros are adapted from videotestsrc.c */
+#define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
+#define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
+#define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
+
+#define I420_Y_OFFSET(w,h) (0)
+#define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
+#define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
+
+#define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
+
+GST_BOILERPLATE (GstTextOverlay, gst_text_overlay, GstElement, GST_TYPE_ELEMENT)
+
+ static void gst_text_overlay_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
@@ -128,7 +131,7 @@ gst_textoverlay_base_init (gpointer g_class)
}
static void
-gst_textoverlay_class_init (GstTextOverlayClass * klass)
+gst_text_overlay_class_init (GstTextOverlayClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
@@ -136,16 +139,19 @@ gst_textoverlay_class_init (GstTextOverlayClass * klass)
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
- parent_class = g_type_class_peek_parent (klass);
+ gobject_class->finalize = gst_text_overlay_finalize;
+ gobject_class->set_property = gst_text_overlay_set_property;
- gobject_class->finalize = gst_textoverlay_finalize;
- gobject_class->set_property = gst_textoverlay_set_property;
- gobject_class->get_property = gst_textoverlay_get_property;
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
- gstelement_class->change_state = gst_textoverlay_change_state;
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
g_param_spec_string ("text", "text",
"Text to be display.", "", G_PARAM_WRITABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING,
+ g_param_spec_boolean ("shaded-background", "shaded background",
+ "Whether to shade the background under the text area", FALSE,
+ G_PARAM_WRITABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
g_param_spec_string ("valign", "vertical alignment",
"Vertical alignment of the text. "
@@ -156,16 +162,22 @@ gst_textoverlay_class_init (GstTextOverlayClass * klass)
"Horizontal alignment of the text. "
"Can be either 'left', 'right', or 'center'",
"center", G_PARAM_WRITABLE));
- g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_X0,
- g_param_spec_int ("x0", "X position",
- "Initial X position."
- " Horizontal aligment takes this point"
- " as reference.", G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
- g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_Y0,
- g_param_spec_int ("y0", "Y position",
- "Initial Y position."
- " Vertical aligment takes this point"
- " as reference.", G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD,
+ g_param_spec_int ("xpad", "horizontal paddding",
+ "Horizontal paddding when using left/right alignment",
+ G_MININT, G_MAXINT, DEFAULT_XPAD, G_PARAM_WRITABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD,
+ g_param_spec_int ("ypad", "vertical padding",
+ "Vertical padding when using top/bottom alignment",
+ G_MININT, G_MAXINT, DEFAULT_YPAD, G_PARAM_WRITABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX,
+ g_param_spec_int ("deltax", "X position modifier",
+ "Shift X position to the left or to the right. Unit is pixels.",
+ G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY,
+ g_param_spec_int ("deltay", "Y position modifier",
+ "Shift Y position up or down. Unit is pixels.",
+ G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
g_param_spec_string ("font-desc", "font description",
"Pango font description of font "
@@ -175,534 +187,716 @@ gst_textoverlay_class_init (GstTextOverlayClass * klass)
" for syntax.", "", G_PARAM_WRITABLE));
}
+static void
+gst_text_overlay_finalize (GObject * object)
+{
+ GstTextOverlay *overlay = GST_TEXT_OVERLAY (object);
+
+ gst_collectpads_stop (overlay->collect);
+ gst_object_unref (overlay->collect);
+
+ g_free (overlay->text_fill_image);
+ g_free (overlay->text_outline_image);
+
+ g_free (overlay->default_text);
+ g_free (overlay->font);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
-#if 0
static void
-resize_bitmap (GstTextOverlay * overlay, int width, int height)
+gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass)
{
- FT_Bitmap *bitmap = &overlay->bitmap;
- int pitch = (width | 3) + 1;
- int size = pitch * height;
-
- /* no need to keep reallocating; just keep the maximum size so far */
- if (size <= overlay->bitmap_buffer_size) {
- bitmap->rows = height;
- bitmap->width = width;
- bitmap->pitch = pitch;
- memset (bitmap->buffer, 0, overlay->bitmap_buffer_size);
- return;
- }
- if (!bitmap->buffer) {
- /* initialize */
- bitmap->pixel_mode = ft_pixel_mode_grays;
- bitmap->num_grays = 256;
+ /* video sink */
+ overlay->video_sinkpad =
+ gst_pad_new_from_template (gst_static_pad_template_get
+ (&video_sink_template_factory), "video_sink");
+ gst_pad_set_getcaps_function (overlay->video_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
+ gst_pad_set_setcaps_function (overlay->video_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps));
+ gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
+
+ /* text sink */
+ overlay->text_sinkpad =
+ gst_pad_new_from_template (gst_static_pad_template_get
+ (&text_sink_template_factory), "text_sink");
+ gst_pad_set_link_function (overlay->text_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked));
+ gst_pad_set_unlink_function (overlay->text_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked));
+ gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
+
+ /* (video) source */
+ overlay->srcpad =
+ gst_pad_new_from_template (gst_static_pad_template_get
+ (&textoverlay_src_template_factory), "src");
+ gst_pad_set_getcaps_function (overlay->srcpad,
+ GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps));
+ gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
+
+ overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
+ overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
+ overlay->xpad = DEFAULT_XPAD;
+ overlay->ypad = DEFAULT_YPAD;
+ overlay->deltax = 0;
+ overlay->deltay = 0;
+
+ overlay->default_text = g_strdup ("");
+ overlay->need_render = TRUE;
+
+ overlay->font = g_strdup (DEFAULT_FONT);
+ overlay->slant = CAIRO_FONT_SLANT_NORMAL;
+ overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
+ overlay->scale = 20;
+ gst_text_overlay_font_init (overlay);
+
+ overlay->framerate = 0.0;
+
+ overlay->collect = gst_collectpads_new ();
+
+ gst_collectpads_set_function (overlay->collect,
+ GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay);
+
+ overlay->video_collect_data = gst_collectpads_add_pad (overlay->collect,
+ overlay->video_sinkpad, sizeof (GstCollectData));
+
+ /* text pad will be added when it is linked */
+ overlay->text_collect_data = NULL;
+}
+
+static void
+gst_text_overlay_font_init (GstTextOverlay * overlay)
+{
+ cairo_font_extents_t font_extents;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256);
+ cr = cairo_create (surface);
+
+ cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
+ cairo_set_font_size (cr, overlay->scale);
+
+ cairo_font_extents (cr, &font_extents);
+ overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height);
+ overlay->need_render = TRUE;
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+}
+
+static void
+gst_text_overlay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstTextOverlay *overlay = GST_TEXT_OVERLAY (object);
+
+ GST_LOCK (overlay);
+
+ switch (prop_id) {
+ case ARG_TEXT:{
+ g_free (overlay->default_text);
+ overlay->default_text = g_value_dup_string (value);
+ break;
+ }
+ case ARG_SHADING:{
+ overlay->want_shading = g_value_get_boolean (value);
+ break;
+ }
+ case ARG_VALIGN:{
+ const gchar *s = g_value_get_string (value);
+
+ if (strcasecmp (s, "baseline") == 0)
+ overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
+ else if (strcasecmp (s, "bottom") == 0)
+ overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM;
+ else if (strcasecmp (s, "top") == 0)
+ overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP;
+ else
+ g_warning ("Invalid 'valign' property value: %s", s);
+ break;
+ }
+ case ARG_HALIGN:{
+ const gchar *s = g_value_get_string (value);
+
+ if (strcasecmp (s, "left") == 0)
+ overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT;
+ else if (strcasecmp (s, "right") == 0)
+ overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
+ else if (strcasecmp (s, "center") == 0)
+ overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
+ else
+ g_warning ("Invalid 'halign' property value: %s", s);
+ break;
+ }
+ case ARG_XPAD:{
+ overlay->xpad = g_value_get_int (value);
+ break;
+ }
+ case ARG_YPAD:{
+ overlay->ypad = g_value_get_int (value);
+ break;
+ }
+ case ARG_DELTAX:{
+ overlay->deltax = g_value_get_int (value);
+ break;
+ }
+ case ARG_DELTAY:{
+ overlay->deltay = g_value_get_int (value);
+ break;
+ }
+ case ARG_FONT_DESC:{
+ g_free (overlay->font);
+ overlay->font = g_value_dup_string (value);
+ if (overlay->font == NULL)
+ overlay->font = g_strdup (DEFAULT_FONT);
+ gst_text_overlay_font_init (overlay);
+ break;
+ }
+ default:{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
}
- if (bitmap->buffer)
- bitmap->buffer = g_realloc (bitmap->buffer, size);
- else
- bitmap->buffer = g_malloc (size);
- bitmap->rows = height;
- bitmap->width = width;
- bitmap->pitch = pitch;
- memset (bitmap->buffer, 0, size);
- overlay->bitmap_buffer_size = size;
+
+ overlay->need_render = TRUE;
+
+ GST_UNLOCK (overlay);
}
-#endif
static void
-gst_textoverlay_render_text (GstTextOverlay * overlay, gchar * text,
- int textlen)
+gst_text_overlay_render_text (GstTextOverlay * overlay, gchar * text,
+ gint textlen)
{
cairo_text_extents_t extents;
- char *string;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ gchar *string;
double x, y;
+ if (textlen < 0)
+ textlen = strlen (text);
+
string = g_strndup (text, textlen);
- if (overlay->text_fill_image)
- g_free (overlay->text_fill_image);
+ if (overlay->need_render) {
+ GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string);
+ } else {
+ GST_DEBUG ("Using previously rendered text.");
+ g_return_if_fail (overlay->text_fill_image != NULL);
+ g_return_if_fail (overlay->text_outline_image != NULL);
+ return;
+ }
+
overlay->text_fill_image =
- g_malloc (4 * overlay->width * overlay->text_height);
- cairo_set_target_image (overlay->cr, overlay->text_fill_image,
- CAIRO_FORMAT_ARGB32, overlay->width, overlay->text_height,
+ g_realloc (overlay->text_fill_image,
+ 4 * overlay->width * overlay->font_height);
+
+ surface = cairo_image_surface_create_for_data (overlay->text_fill_image,
+ CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
overlay->width * 4);
- cairo_save (overlay->cr);
- cairo_rectangle (overlay->cr, 0, 0, overlay->width, overlay->text_height);
- cairo_set_rgb_color (overlay->cr, 0, 0, 0);
- cairo_set_alpha (overlay->cr, 1.0);
- cairo_set_operator (overlay->cr, CAIRO_OPERATOR_SRC);
- cairo_fill (overlay->cr);
- cairo_restore (overlay->cr);
-
- cairo_save (overlay->cr);
- cairo_text_extents (overlay->cr, string, &extents);
- cairo_set_rgb_color (overlay->cr, 1, 1, 1);
- cairo_set_alpha (overlay->cr, 1.0);
+ cr = cairo_create (surface);
+
+ cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
+ cairo_set_font_size (cr, overlay->scale);
+
+ cairo_save (cr);
+ cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
+ cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+ cairo_save (cr);
+ cairo_text_extents (cr, string, &extents);
+ cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
+
switch (overlay->halign) {
case GST_TEXT_OVERLAY_HALIGN_LEFT:
- x = overlay->x0;
+ x = overlay->xpad;
break;
case GST_TEXT_OVERLAY_HALIGN_CENTER:
- x = overlay->x0 - extents.width / 2;
+ x = (overlay->width - extents.width) / 2;
break;
case GST_TEXT_OVERLAY_HALIGN_RIGHT:
- x = overlay->x0 - extents.width;
+ x = overlay->width - extents.width - overlay->xpad;
break;
default:
x = 0;
}
- y = overlay->text_height - 2;
- cairo_move_to (overlay->cr, x, y);
- cairo_show_text (overlay->cr, string);
- cairo_restore (overlay->cr);
+ x += overlay->deltax;
+
+ overlay->text_x0 = x;
+ overlay->text_x1 = x + extents.x_advance;
+
+ overlay->text_dy = (extents.height + extents.y_bearing);
+ y = overlay->font_height - overlay->text_dy;
+
+ cairo_move_to (cr, x, y);
+ cairo_show_text (cr, string);
+ cairo_restore (cr);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+
+ /* ----------- */
- if (overlay->text_outline_image)
- g_free (overlay->text_outline_image);
overlay->text_outline_image =
- g_malloc (4 * overlay->width * overlay->text_height);
- cairo_set_target_image (overlay->cr, overlay->text_outline_image,
- CAIRO_FORMAT_ARGB32, overlay->width, overlay->text_height,
+ g_realloc (overlay->text_outline_image,
+ 4 * overlay->width * overlay->font_height);
+
+ surface = cairo_image_surface_create_for_data (overlay->text_outline_image,
+ CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height,
overlay->width * 4);
- cairo_save (overlay->cr);
- cairo_rectangle (overlay->cr, 0, 0, overlay->width, overlay->text_height);
- cairo_set_rgb_color (overlay->cr, 0, 0, 0);
- cairo_set_alpha (overlay->cr, 1.0);
- cairo_set_operator (overlay->cr, CAIRO_OPERATOR_SRC);
- cairo_fill (overlay->cr);
- cairo_restore (overlay->cr);
-
- cairo_save (overlay->cr);
- cairo_move_to (overlay->cr, x, y);
- cairo_set_rgb_color (overlay->cr, 1, 1, 1);
- cairo_set_alpha (overlay->cr, 1.0);
- cairo_set_line_width (overlay->cr, 1.0);
- cairo_text_path (overlay->cr, string);
- cairo_stroke (overlay->cr);
- cairo_restore (overlay->cr);
+ cr = cairo_create (surface);
+
+ cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight);
+ cairo_set_font_size (cr, overlay->scale);
+
+ cairo_save (cr);
+ cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height);
+ cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+ cairo_save (cr);
+ cairo_move_to (cr, x, y);
+ cairo_set_source_rgba (cr, 1, 1, 1, 1.0);
+ cairo_set_line_width (cr, 1.0);
+ cairo_text_path (cr, string);
+ cairo_stroke (cr);
+ cairo_restore (cr);
g_free (string);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+
+ overlay->need_render = FALSE;
}
-/* static GstPadLinkReturn */
-/* gst_textoverlay_text_sinkconnect (GstPad *pad, GstCaps *caps) */
-/* { */
-/* return GST_PAD_LINK_DONE; */
-/* } */
+static GstCaps *
+gst_text_overlay_getcaps (GstPad * pad)
+{
+ GstTextOverlay *overlay;
+ GstPad *otherpad;
+ GstCaps *caps;
+ overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
-static GstPadLinkReturn
-gst_textoverlay_video_sinkconnect (GstPad * pad, const GstCaps * caps)
+ if (pad == overlay->srcpad)
+ otherpad = overlay->video_sinkpad;
+ else
+ otherpad = overlay->srcpad;
+
+ /* we can do what the peer can */
+ caps = gst_pad_peer_get_caps (otherpad);
+ if (caps) {
+ GstCaps *temp;
+ const GstCaps *templ;
+
+ GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps);
+
+ /* filtered against our padtemplate */
+ templ = gst_pad_get_pad_template_caps (otherpad);
+ GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ);
+ temp = gst_caps_intersect (caps, templ);
+ GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
+ gst_caps_unref (caps);
+ /* this is what we can do */
+ caps = temp;
+ } else {
+ /* no peer, our padtemplate is enough then */
+ caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
+ }
+
+ GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
+
+ gst_object_unref (overlay);
+
+ return caps;
+}
+
+/* FIXME: upstream nego (e.g. when the video window is resized) */
+
+static gboolean
+gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps)
{
GstTextOverlay *overlay;
GstStructure *structure;
+ gboolean ret = FALSE;
+ gdouble fps = 0.0;
+
+ if (!GST_PAD_IS_SINK (pad))
+ return TRUE;
+
+ g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
- overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
+ overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
+ overlay->width = 0;
+ overlay->height = 0;
structure = gst_caps_get_structure (caps, 0);
- overlay->width = overlay->height = 0;
- gst_structure_get_int (structure, "width", &overlay->width);
- gst_structure_get_int (structure, "height", &overlay->height);
+ if (gst_structure_get_int (structure, "width", &overlay->width) &&
+ gst_structure_get_int (structure, "height", &overlay->height)) {
+ ret = gst_pad_set_caps (overlay->srcpad, caps);
+ }
+
+ (void) gst_structure_get_double (structure, "framerate", &fps);
+ overlay->framerate = fps;
- return gst_pad_try_set_caps (overlay->srcpad, caps);
+ return ret;
}
+static GstPadLinkReturn
+gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
+{
+ GstTextOverlay *overlay;
+
+ overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad));
+
+ GST_DEBUG_OBJECT (overlay, "Text pad linked");
+
+ if (overlay->text_collect_data == NULL) {
+ overlay->text_collect_data = gst_collectpads_add_pad (overlay->collect,
+ overlay->text_sinkpad, sizeof (GstCollectData));
+ }
+
+ overlay->need_render = TRUE;
+
+ return GST_PAD_LINK_OK;
+}
static void
+gst_text_overlay_text_pad_unlinked (GstPad * pad)
+{
+ GstTextOverlay *overlay;
+
+ /* don't use gst_pad_get_parent() here, will deadlock */
+ overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad));
+
+ GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
+
+ if (overlay->text_collect_data) {
+ gst_collectpads_remove_pad (overlay->collect, overlay->text_sinkpad);
+ overlay->text_collect_data = NULL;
+ }
+
+ overlay->need_render = TRUE;
+}
+
+#define BOX_SHADING_VAL -80
+#define BOX_XPAD 6
+#define BOX_YPAD 6
+
+static inline void
+gst_text_overlay_shade_y (GstTextOverlay * overlay, guchar * dest,
+ guint dest_stride, gint y0, gint y1)
+{
+ gint i, j, x0, x1;
+
+ x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width);
+ x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width);
+
+ y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
+ y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
+
+ for (i = y0; i < y1; ++i) {
+ for (j = x0; j < x1; ++j) {
+ gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL;
+
+ dest[(i * dest_stride) + j] = CLAMP (y, 0, 255);
+ }
+ }
+}
+
+static inline void
gst_text_overlay_blit_1 (GstTextOverlay * overlay, guchar * dest,
- guchar * text_image, int val)
+ guchar * text_image, gint val, guint dest_stride)
{
- int i;
- int j;
- int x, a, y;
- int y0 = 0;
+ gint i, j;
+ gint x, a, y;
+ gint y0 = 0;
y = val;
- for (i = 0; i < overlay->text_height; i++) {
+ for (i = 0; i < overlay->font_height; i++) {
for (j = 0; j < overlay->width; j++) {
- x = dest[(i + y0) * overlay->width + j];
+ x = dest[(i + y0) * dest_stride + j];
a = text_image[4 * (i * overlay->width + j) + 1];
- dest[(i + y0) * overlay->width + j] = (y * a + x * (255 - a)) / 255;
+ dest[(i + y0) * dest_stride + j] = (y * a + x * (255 - a)) / 255;
}
}
}
-static void
+static inline void
gst_text_overlay_blit_sub2x2 (GstTextOverlay * overlay, guchar * dest,
- guchar * text_image, int val)
+ guchar * text_image, gint val, guint dest_stride)
{
- int i;
- int j;
- int x, a, y;
- int y0 = 0;
+ gint i, j;
+ gint x, a, y;
+ gint y0 = 0;
y = val;
- for (i = 0; i < overlay->text_height; i += 2) {
+ for (i = 0; i < overlay->font_height; i += 2) {
for (j = 0; j < overlay->width; j += 2) {
- x = dest[(i / 2 + y0) * (overlay->width / 2) + j / 2];
+ x = dest[(i / 2 + y0) * dest_stride + j / 2];
a = (text_image[4 * (i * overlay->width + j) + 1] +
text_image[4 * (i * overlay->width + j + 1) + 1] +
text_image[4 * ((i + 1) * overlay->width + j) + 1] +
text_image[4 * ((i + 1) * overlay->width + j + 1) + 1] + 2) / 4;
- dest[(i / 2 + y0) * (overlay->width / 2) + j / 2] =
- (y * a + x * (255 - a)) / 255;
+ dest[(i / 2 + y0) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
}
}
}
-static void
-gst_textoverlay_video_chain (GstPad * pad, GstData * _data)
+static GstFlowReturn
+gst_text_overlay_push_frame (GstTextOverlay * overlay, GstBuffer * video_frame)
{
- GstBuffer *buf = GST_BUFFER (_data);
- GstTextOverlay *overlay;
- guchar *pixbuf;
- gint y;
+ guchar *y, *u, *v;
+ gint ypos;
- g_return_if_fail (pad != NULL);
- g_return_if_fail (GST_IS_PAD (pad));
- g_return_if_fail (buf != NULL);
- overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
- g_return_if_fail (overlay != NULL);
- g_return_if_fail (GST_IS_TEXTOVERLAY (overlay));
+ video_frame = gst_buffer_make_writable (video_frame);
- if (!GST_IS_BUFFER (_data))
- return;
-
- pixbuf = GST_BUFFER_DATA (buf);
-
- y = overlay->y0;
switch (overlay->valign) {
case GST_TEXT_OVERLAY_VALIGN_BOTTOM:
- y -= overlay->text_height;
+ ypos = overlay->height - overlay->font_height - overlay->ypad;
break;
case GST_TEXT_OVERLAY_VALIGN_BASELINE:
-#define BASELINE 2
- y -= (overlay->text_height - BASELINE);
+ ypos = overlay->height - (overlay->font_height - overlay->text_dy)
+ - overlay->ypad;
break;
case GST_TEXT_OVERLAY_VALIGN_TOP:
+ ypos = overlay->ypad;
+ break;
+ default:
+ ypos = overlay->ypad;
break;
}
+ ypos += overlay->deltay;
+
+ y = GST_BUFFER_DATA (video_frame);
+ u = y + I420_U_OFFSET (overlay->width, overlay->height);
+ v = y + I420_V_OFFSET (overlay->width, overlay->height);
+
+ /* shaded background box */
+ if (overlay->want_shading) {
+ gst_text_overlay_shade_y (overlay,
+ y, I420_Y_ROWSTRIDE (overlay->width),
+ ypos + overlay->text_dy, ypos + overlay->font_height);
+ }
+
+ /* blit outline text on video image */
gst_text_overlay_blit_1 (overlay,
- pixbuf + y * overlay->width, overlay->text_outline_image, 0);
+ y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width),
+ overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width));
+ gst_text_overlay_blit_sub2x2 (overlay,
+ u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
+ overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width));
gst_text_overlay_blit_sub2x2 (overlay,
- pixbuf + (overlay->height * overlay->width) +
- (y / 2) * overlay->width / 2, overlay->text_outline_image, 128);
- gst_text_overlay_blit_sub2x2 (overlay, pixbuf +
- (overlay->height * overlay->width) +
- (overlay->height * overlay->width) / 4 + (y / 2) * overlay->width / 2,
- overlay->text_outline_image, 128);
-
- gst_text_overlay_blit_1 (overlay, pixbuf + y * overlay->width,
- overlay->text_fill_image, 255);
+ v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
+ overlay->text_outline_image, 128, I420_V_ROWSTRIDE (overlay->width));
+
+ /* blit text on video image */
+ gst_text_overlay_blit_1 (overlay,
+ y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width),
+ overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width));
gst_text_overlay_blit_sub2x2 (overlay,
- pixbuf + (overlay->height * overlay->width) +
- (y / 2) * overlay->width / 2, overlay->text_fill_image, 128);
+ u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
+ overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width));
gst_text_overlay_blit_sub2x2 (overlay,
- pixbuf + (overlay->height * overlay->width) +
- (overlay->height * overlay->width) / 4 + (y / 2) * overlay->width / 2,
- overlay->text_fill_image, 128);
+ v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
+ overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width));
- gst_pad_push (overlay->srcpad, GST_DATA (buf));
+ return gst_pad_push (overlay->srcpad, video_frame);
}
-#define PAST_END(buffer, time) \
- (GST_BUFFER_TIMESTAMP (buffer) != GST_CLOCK_TIME_NONE && \
- GST_BUFFER_DURATION (buffer) != GST_CLOCK_TIME_NONE && \
- GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) \
- < (time))
-
static void
-gst_textoverlay_loop (GstElement * element)
+gst_text_overlay_pop_video (GstTextOverlay * overlay)
{
- GstTextOverlay *overlay;
- GstBuffer *video_frame;
- guint64 now;
-
- g_return_if_fail (element != NULL);
- g_return_if_fail (GST_IS_TEXTOVERLAY (element));
- overlay = GST_TEXTOVERLAY (element);
-
- do {
- GST_DEBUG ("Attempting to pull next video frame");
- video_frame = GST_BUFFER (gst_pad_pull (overlay->video_sinkpad));
- if (GST_IS_EVENT (video_frame)) {
- GstEvent *event = GST_EVENT (video_frame);
- GstEventType type = GST_EVENT_TYPE (event);
-
- gst_pad_event_default (overlay->video_sinkpad, event);
- GST_DEBUG ("Received event of type %d", type);
- if (type == GST_EVENT_EOS || type == GST_EVENT_INTERRUPT)
- return;
- video_frame = NULL;
- }
- } while (!video_frame);
- now = GST_BUFFER_TIMESTAMP (video_frame);
- GST_DEBUG ("Got video frame, time=%" GST_TIME_FORMAT, GST_TIME_ARGS (now));
-
- /*
- * This state machine has a bug that can't be resolved easily.
- * (Needs a more complicated state machine.) Basically, if the
- * text that came from a buffer from the sink pad is being
- * displayed, and the default text is changed by set_parameter,
- * we'll incorrectly display the default text.
- *
- * Otherwise, this is a pretty decent state machine that handles
- * buffer timestamps and durations correctly. (I think)
- */
-
- while ((!overlay->current_buffer ||
- PAST_END (overlay->current_buffer, now)) &&
- overlay->next_buffer == NULL) {
- GST_DEBUG ("attempting to pull a buffer");
-
- /* read all text buffers until we get one "in the future" */
- if (!GST_PAD_IS_USABLE (overlay->text_sinkpad)) {
- break;
- }
- do {
- overlay->next_buffer = GST_BUFFER (gst_pad_pull (overlay->text_sinkpad));
- if (GST_IS_EVENT (overlay->next_buffer)) {
- GstEvent *event = GST_EVENT (overlay->next_buffer);
- GstEventType type = GST_EVENT_TYPE (event);
-
- gst_pad_event_default (overlay->text_sinkpad, event);
- if (type == GST_EVENT_EOS || type == GST_EVENT_INTERRUPT)
- return;
- overlay->next_buffer = NULL;
- }
- } while (!overlay->next_buffer);
-
- if (PAST_END (overlay->next_buffer, now)) {
- GST_DEBUG ("Received buffer is past end (%" GST_TIME_FORMAT " + %"
- GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")",
- GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (overlay->next_buffer)),
- GST_TIME_ARGS (GST_BUFFER_DURATION (overlay->next_buffer)),
- GST_TIME_ARGS (now));
- gst_buffer_unref (overlay->next_buffer);
- overlay->next_buffer = NULL;
- } else {
- GST_DEBUG ("Received new text buffer of time %" GST_TIME_FORMAT
- "and duration %" GST_TIME_FORMAT,
- GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (overlay->next_buffer)),
- GST_TIME_ARGS (GST_BUFFER_DURATION (overlay->next_buffer)));
- }
- }
-
- if (overlay->next_buffer &&
- (GST_BUFFER_TIMESTAMP (overlay->next_buffer) <= now ||
- GST_BUFFER_TIMESTAMP (overlay->next_buffer) == GST_CLOCK_TIME_NONE)) {
- GST_DEBUG ("using new buffer");
-
- if (overlay->current_buffer) {
- gst_buffer_unref (overlay->current_buffer);
- }
- overlay->current_buffer = overlay->next_buffer;
- overlay->next_buffer = NULL;
-
- GST_DEBUG ("rendering '%*s'",
- GST_BUFFER_SIZE (overlay->current_buffer),
- GST_BUFFER_DATA (overlay->current_buffer));
- gst_textoverlay_render_text (overlay,
- GST_BUFFER_DATA (overlay->current_buffer),
- GST_BUFFER_SIZE (overlay->current_buffer));
- overlay->need_render = FALSE;
- }
-
- if (overlay->current_buffer && PAST_END (overlay->current_buffer, now)) {
- GST_DEBUG ("dropping old buffer");
-
- gst_buffer_unref (overlay->current_buffer);
- overlay->current_buffer = NULL;
-
- overlay->need_render = TRUE;
- }
+ GstBuffer *buf;
- if (overlay->need_render) {
- GST_DEBUG ("rendering '%s'", overlay->default_text);
- gst_textoverlay_render_text (overlay,
- overlay->default_text, strlen (overlay->default_text));
-
- overlay->need_render = FALSE;
- }
-
- gst_textoverlay_video_chain (overlay->srcpad, GST_DATA (video_frame));
+ buf = gst_collectpads_pop (overlay->collect, overlay->video_collect_data);
+ g_return_if_fail (buf != NULL);
+ gst_buffer_unref (buf);
}
static void
-gst_textoverlay_font_init (GstTextOverlay * overlay)
+gst_text_overlay_pop_text (GstTextOverlay * overlay)
{
- cairo_font_extents_t font_extents;
-
- cairo_select_font (overlay->cr, overlay->font, overlay->slant,
- overlay->weight);
- cairo_scale_font (overlay->cr, overlay->scale);
+ GstBuffer *buf;
- cairo_current_font_extents (overlay->cr, &font_extents);
- overlay->text_height = font_extents.height;
- if (overlay->text_height & 1)
- overlay->text_height++;
+ if (overlay->text_collect_data) {
+ buf = gst_collectpads_pop (overlay->collect, overlay->text_collect_data);
+ g_return_if_fail (buf != NULL);
+ gst_buffer_unref (buf);
+ }
overlay->need_render = TRUE;
}
-static GstStateChangeReturn
-gst_textoverlay_change_state (GstElement * element, GstStateChange transition)
+/* This function is called when there is data on all pads */
+static GstFlowReturn
+gst_text_overlay_collected (GstCollectPads * pads, gpointer data)
{
GstTextOverlay *overlay;
+ GstFlowReturn ret = GST_FLOW_OK;
+ GstClockTime now, txt_end, frame_end;
+ GstBuffer *video_frame = NULL;
+ GstBuffer *text_buf = NULL;
- overlay = GST_TEXTOVERLAY (element);
-
- switch (transition) {
- case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
- break;
- case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
- break;
- case GST_STATE_CHANGE_PAUSED_TO_READY:
- break;
- default:
- break;
- }
-
- parent_class->change_state (element, transition);
+ overlay = GST_TEXT_OVERLAY (data);
- return GST_STATE_CHANGE_SUCCESS;
-}
+ GST_DEBUG ("Collecting");
-static void
-gst_textoverlay_finalize (GObject * object)
-{
- GstTextOverlay *overlay = GST_TEXTOVERLAY (object);
+ video_frame = gst_collectpads_peek (overlay->collect,
+ overlay->video_collect_data);
- if (overlay->cr) {
- cairo_destroy (overlay->cr);
+ /* send EOS if video stream EOSed regardless of text stream */
+ if (video_frame == NULL) {
+ GST_DEBUG ("Video stream at EOS");
+ if (overlay->text_collect_data) {
+ text_buf = gst_collectpads_pop (overlay->collect,
+ overlay->text_collect_data);
+ }
+ gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
+ ret = GST_FLOW_UNEXPECTED;
+ goto done;
}
- G_OBJECT_CLASS (parent_class)->finalize (object);
-}
+ if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
+ g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
+ }
-static void
-gst_textoverlay_init (GstTextOverlay * overlay)
-{
- /* video sink */
- overlay->video_sinkpad =
- gst_pad_new_from_template (gst_static_pad_template_get
- (&video_sink_template_factory), "video_sink");
- gst_pad_set_link_function (overlay->video_sinkpad,
- gst_textoverlay_video_sinkconnect);
- gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
+ now = GST_BUFFER_TIMESTAMP (video_frame);
- /* text sink */
- overlay->text_sinkpad =
- gst_pad_new_from_template (gst_static_pad_template_get
- (&text_sink_template_factory), "text_sink");
- gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
+ if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) {
+ frame_end = now + GST_BUFFER_DURATION (video_frame);
+ } else if (overlay->framerate > 0.0) {
+ frame_end = now + (GST_SECOND / overlay->framerate);
+ } else {
+ /* magic value, does not really matter since texts
+ * tend to span quite a few frames in practice anyway */
+ frame_end = now + GST_SECOND / 25;
+ }
- /* (video) source */
- overlay->srcpad =
- gst_pad_new_from_template (gst_static_pad_template_get
- (&textoverlay_src_template_factory), "src");
- gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
+ GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
- overlay->cr = cairo_create ();
+ /* text pad not linked? */
+ if (overlay->text_collect_data == NULL) {
+ GST_DEBUG ("Text pad not linked, rendering default text: '%s'",
+ GST_STR_NULL (overlay->default_text));
+ if (overlay->default_text && *overlay->default_text != '\0') {
+ gst_text_overlay_render_text (overlay, overlay->default_text, -1);
+ ret = gst_text_overlay_push_frame (overlay, video_frame);
+ } else {
+ ret = gst_pad_push (overlay->srcpad, video_frame);
+ }
+ gst_text_overlay_pop_video (overlay);
+ video_frame = NULL;
+ goto done;
+ }
- overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
- overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
- overlay->x0 = overlay->y0 = 25;
+ text_buf = gst_collectpads_peek (overlay->collect,
+ overlay->text_collect_data);
- overlay->default_text = g_strdup ("");
- overlay->need_render = TRUE;
+ /* just push the video frame if the text stream has EOSed */
+ if (text_buf == NULL) {
+ GST_DEBUG ("Text pad EOSed, just pushing video frame as is");
+ ret = gst_pad_push (overlay->srcpad, video_frame);
+ gst_text_overlay_pop_video (overlay);
+ video_frame = NULL;
+ goto done;
+ }
- overlay->font = g_strdup ("sans");
- overlay->slant = CAIRO_FONT_SLANT_NORMAL;
- overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
- overlay->scale = 20;
- gst_textoverlay_font_init (overlay);
+ /* if the text buffer isn't stamped right, pop it off the
+ * queue and display it for the current video frame only */
+ if (GST_BUFFER_TIMESTAMP (text_buf) == GST_CLOCK_TIME_NONE ||
+ GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) {
+ GST_WARNING ("Got text buffer with invalid time stamp or duration");
+ gst_text_overlay_pop_text (overlay);
+ GST_BUFFER_TIMESTAMP (text_buf) = now;
+ GST_BUFFER_DURATION (text_buf) = frame_end - now;
+ }
- gst_element_set_loop_function (GST_ELEMENT (overlay), gst_textoverlay_loop);
-}
+ txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
-static void
-gst_textoverlay_set_property (GObject * object, guint prop_id,
- const GValue * value, GParamSpec * pspec)
-{
- GstTextOverlay *overlay;
+ GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
- g_return_if_fail (GST_IS_TEXTOVERLAY (object));
- overlay = GST_TEXTOVERLAY (object);
+ /* if the text buffer is too old, pop it off the
+ * queue and return so we get a new one next time */
+ if (txt_end < now) {
+ GST_DEBUG ("Text buffer too old, popping off the queue");
+ gst_text_overlay_pop_text (overlay);
+ ret = GST_FLOW_OK;
+ goto done;
+ }
- switch (prop_id) {
+ /* if the video frame ends before the text even starts,
+ * just push it out as is and pop it off the queue */
+ if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) {
+ GST_DEBUG ("Video buffer before text, pushing out and popping off queue");
+ ret = gst_pad_push (overlay->srcpad, video_frame);
+ gst_text_overlay_pop_video (overlay);
+ video_frame = NULL;
+ goto done;
+ }
- case ARG_TEXT:
- if (overlay->default_text) {
- g_free (overlay->default_text);
- }
- overlay->default_text = g_strdup (g_value_get_string (value));
- overlay->need_render = TRUE;
- break;
+ /* text duration overlaps video frame duration */
+ GST_DEBUG ("Rendering '%*s'",
+ GST_BUFFER_SIZE (text_buf), GST_BUFFER_DATA (text_buf));
+ gst_text_overlay_pop_video (overlay);
+ ret = gst_pad_push (overlay->srcpad, video_frame);
+ video_frame = NULL;
+ goto done;
- case ARG_VALIGN:
- if (strcasecmp (g_value_get_string (value), "baseline") == 0)
- overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
- else if (strcasecmp (g_value_get_string (value), "bottom") == 0)
- overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM;
- else if (strcasecmp (g_value_get_string (value), "top") == 0)
- overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP;
- else
- g_warning ("Invalid 'valign' property value: %s",
- g_value_get_string (value));
- overlay->need_render = TRUE;
- break;
+done:
+ {
+ if (text_buf)
+ gst_buffer_unref (text_buf);
- case ARG_HALIGN:
- if (strcasecmp (g_value_get_string (value), "left") == 0)
- overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT;
- else if (strcasecmp (g_value_get_string (value), "right") == 0)
- overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
- else if (strcasecmp (g_value_get_string (value), "center") == 0)
- overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
- else
- g_warning ("Invalid 'halign' property value: %s",
- g_value_get_string (value));
- overlay->need_render = TRUE;
- break;
+ if (video_frame)
+ gst_buffer_unref (video_frame);
- case ARG_X0:
- overlay->x0 = g_value_get_int (value);
- break;
+ return ret;
+ }
+}
- case ARG_Y0:
- overlay->y0 = g_value_get_int (value);
- break;
+static GstStateChangeReturn
+gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+ GstTextOverlay *overlay = GST_TEXT_OVERLAY (element);
- case ARG_FONT_DESC:
- if (overlay->font)
- g_free (overlay->font);
- overlay->font = g_strdup (g_value_get_string (value));
- gst_textoverlay_font_init (overlay);
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ gst_collectpads_start (overlay->collect);
break;
-
default:
break;
}
-}
-static void
-gst_textoverlay_get_property (GObject * object, guint prop_id, GValue * value,
- GParamSpec * pspec)
-{
- GstTextOverlay *overlay;
-
- g_return_if_fail (GST_IS_TEXTOVERLAY (object));
- overlay = GST_TEXTOVERLAY (object);
+ ret = parent_class->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
- switch (prop_id) {
+ switch (transition) {
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ gst_collectpads_stop (overlay->collect);
+ break;
default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
+
+ return ret;
}