diff --git a/ext/pango/Makefile.am b/ext/pango/Makefile.am index 2ffea22685..417173b180 100644 --- a/ext/pango/Makefile.am +++ b/ext/pango/Makefile.am @@ -1,12 +1,14 @@ plugin_LTLIBRARIES = libgstpango.la noinst_HEADERS = \ + gstbasetextoverlay.h \ gstclockoverlay.h \ gsttextoverlay.h \ gsttextrender.h \ gsttimeoverlay.h libgstpango_la_SOURCES = \ + gstbasetextoverlay.c \ gstclockoverlay.c \ gsttextoverlay.c \ gsttextrender.c \ diff --git a/ext/pango/gstbasetextoverlay.c b/ext/pango/gstbasetextoverlay.c new file mode 100644 index 0000000000..82c390a47e --- /dev/null +++ b/ext/pango/gstbasetextoverlay.c @@ -0,0 +1,2727 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2003> David Schleef + * Copyright (C) <2006> Julien Moutte + * Copyright (C) <2006> Zeeshan Ali + * Copyright (C) <2006-2008> Tim-Philipp Müller + * Copyright (C) <2009> Young-Ho Cha + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-textoverlay + * @see_also: #GstTextRender, #GstClockOverlay, #GstTimeOverlay, #GstSubParse + * + * This plugin renders text on top of a video stream. This can be either + * static text or text from buffers received on the text sink pad, e.g. + * as produced by the subparse element. If the text sink pad is not linked, + * the text set via the "text" property will be rendered. If the text sink + * pad is linked, text will be rendered as it is received on that pad, + * honouring and matching the buffer timestamps of both input streams. + * + * The text can contain newline characters and text wrapping is enabled by + * default. + * + * + * Example launch lines + * |[ + * gst-launch -v videotestsrc ! textoverlay text="Room A" valign=top halign=left ! xvimagesink + * ]| Here is a simple pipeline that displays a static text in the top left + * corner of the video picture + * |[ + * gst-launch -v filesrc location=subtitles.srt ! subparse ! txt. videotestsrc ! timeoverlay ! textoverlay name=txt shaded-background=yes ! xvimagesink + * ]| Here is another pipeline that displays subtitles from an .srt subtitle + * file, centered at the bottom of the picture and with a rectangular shading + * around the text in the background: + * + * If you do not have such a subtitle file, create one looking like this + * in a text editor: + * |[ + * 1 + * 00:00:03,000 --> 00:00:05,000 + * Hello? (3-5s) + * + * 2 + * 00:00:08,000 --> 00:00:13,000 + * Yes, this is a subtitle. Don't + * you like it? (8-13s) + * + * 3 + * 00:00:18,826 --> 00:01:02,886 + * Uh? What are you talking about? + * I don't understand (18-62s) + * ]| + * + * + */ + +/* FIXME: alloc segment as part of instance struct */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstbasetextoverlay.h" +#include "gsttextoverlay.h" +#include "gsttimeoverlay.h" +#include "gstclockoverlay.h" +#include "gsttextrender.h" +#include + +/* FIXME: + * - use proper strides and offset for I420 + * - if text is wider than the video picture, it does not get + * clipped properly during blitting (if wrapping is disabled) + * - make 'shading_value' a property (or enum: light/normal/dark/verydark)? + */ + +GST_DEBUG_CATEGORY (pango_debug); +#define GST_CAT_DEFAULT pango_debug + +#define DEFAULT_PROP_TEXT "" +#define DEFAULT_PROP_SHADING FALSE +#define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE +#define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER +#define DEFAULT_PROP_VALIGN "baseline" +#define DEFAULT_PROP_HALIGN "center" +#define DEFAULT_PROP_XPAD 25 +#define DEFAULT_PROP_YPAD 25 +#define DEFAULT_PROP_DELTAX 0 +#define DEFAULT_PROP_DELTAY 0 +#define DEFAULT_PROP_XPOS 0.5 +#define DEFAULT_PROP_YPOS 0.5 +#define DEFAULT_PROP_WRAP_MODE GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR +#define DEFAULT_PROP_FONT_DESC "" +#define DEFAULT_PROP_SILENT FALSE +#define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER +#define DEFAULT_PROP_WAIT_TEXT TRUE +#define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE +#define DEFAULT_PROP_VERTICAL_RENDER FALSE +#define DEFAULT_PROP_COLOR 0xffffffff + +/* make a property of me */ +#define DEFAULT_SHADING_VALUE -80 + +#define MINIMUM_OUTLINE_OFFSET 1.0 +#define DEFAULT_SCALE_BASIS 640 + +#define COMP_Y(ret, r, g, b) \ +{ \ + ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \ + ret = CLAMP (ret, 0, 255); \ +} + +#define COMP_U(ret, r, g, b) \ +{ \ + ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \ + ret = CLAMP (ret, 0, 255); \ +} + +#define COMP_V(ret, r, g, b) \ +{ \ + ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \ + ret = CLAMP (ret, 0, 255); \ +} + +#define BLEND(ret, alpha, v0, v1) \ +{ \ + ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \ +} + +#define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew) \ +{ \ + gint _tmp; \ + _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \ + ret = CLAMP (_tmp, 0, 255); \ +} + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +# define CAIRO_ARGB_A 3 +# define CAIRO_ARGB_R 2 +# define CAIRO_ARGB_G 1 +# define CAIRO_ARGB_B 0 +#else +# define CAIRO_ARGB_A 0 +# define CAIRO_ARGB_R 1 +# define CAIRO_ARGB_G 2 +# define CAIRO_ARGB_B 3 +#endif + +enum +{ + PROP_0, + PROP_TEXT, + PROP_SHADING, + PROP_VALIGN, /* deprecated */ + PROP_HALIGN, /* deprecated */ + PROP_HALIGNMENT, + PROP_VALIGNMENT, + PROP_XPAD, + PROP_YPAD, + PROP_DELTAX, + PROP_DELTAY, + PROP_XPOS, + PROP_YPOS, + PROP_WRAP_MODE, + PROP_FONT_DESC, + PROP_SILENT, + PROP_LINE_ALIGNMENT, + PROP_WAIT_TEXT, + PROP_AUTO_ADJUST_SIZE, + PROP_VERTICAL_RENDER, + PROP_COLOR, + PROP_LAST +}; + +static GstStaticPadTemplate src_template_factory = + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx ";" + GST_VIDEO_CAPS_RGBx ";" + GST_VIDEO_CAPS_xRGB ";" + GST_VIDEO_CAPS_xBGR ";" + GST_VIDEO_CAPS_RGBA ";" + GST_VIDEO_CAPS_BGRA ";" + GST_VIDEO_CAPS_ARGB ";" + GST_VIDEO_CAPS_ABGR ";" + GST_VIDEO_CAPS_YUV ("{AYUV, I420, UYVY, NV12, NV21}")) + ); + +static GstStaticPadTemplate video_sink_template_factory = + GST_STATIC_PAD_TEMPLATE ("video_sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx ";" + GST_VIDEO_CAPS_RGBx ";" + GST_VIDEO_CAPS_xRGB ";" + GST_VIDEO_CAPS_xBGR ";" + GST_VIDEO_CAPS_RGBA ";" + GST_VIDEO_CAPS_BGRA ";" + GST_VIDEO_CAPS_ARGB ";" + GST_VIDEO_CAPS_ABGR ";" + GST_VIDEO_CAPS_YUV ("{AYUV, I420, UYVY, NV12, NV21}")) + ); + +#define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type()) +static GType +gst_base_text_overlay_valign_get_type (void) +{ + static GType base_text_overlay_valign_type = 0; + static const GEnumValue base_text_overlay_valign[] = { + {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"}, + {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"}, + {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"}, + {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"}, + {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"}, + {0, NULL, NULL}, + }; + + if (!base_text_overlay_valign_type) { + base_text_overlay_valign_type = + g_enum_register_static ("GstBaseTextOverlayVAlign", + base_text_overlay_valign); + } + return base_text_overlay_valign_type; +} + +#define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type()) +static GType +gst_base_text_overlay_halign_get_type (void) +{ + static GType base_text_overlay_halign_type = 0; + static const GEnumValue base_text_overlay_halign[] = { + {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"}, + {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"}, + {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"}, + {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"}, + {0, NULL, NULL}, + }; + + if (!base_text_overlay_halign_type) { + base_text_overlay_halign_type = + g_enum_register_static ("GstBaseTextOverlayHAlign", + base_text_overlay_halign); + } + return base_text_overlay_halign_type; +} + + +#define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type()) +static GType +gst_base_text_overlay_wrap_mode_get_type (void) +{ + static GType base_text_overlay_wrap_mode_type = 0; + static const GEnumValue base_text_overlay_wrap_mode[] = { + {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"}, + {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"}, + {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"}, + {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"}, + {0, NULL, NULL}, + }; + + if (!base_text_overlay_wrap_mode_type) { + base_text_overlay_wrap_mode_type = + g_enum_register_static ("GstBaseTextOverlayWrapMode", + base_text_overlay_wrap_mode); + } + return base_text_overlay_wrap_mode_type; +} + +#define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type()) +static GType +gst_base_text_overlay_line_align_get_type (void) +{ + static GType base_text_overlay_line_align_type = 0; + static const GEnumValue base_text_overlay_line_align[] = { + {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"}, + {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"}, + {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"}, + {0, NULL, NULL} + }; + + if (!base_text_overlay_line_align_type) { + base_text_overlay_line_align_type = + g_enum_register_static ("GstBaseTextOverlayLineAlign", + base_text_overlay_line_align); + } + return base_text_overlay_line_align_type; +} + +#define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (((GstBaseTextOverlay *)ov)->cond) +#define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_OBJECT_GET_LOCK (ov))) +#define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov))) +#define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov))) + +static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement * + element, GstStateChange transition); + +static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad); +static gboolean gst_base_text_overlay_setcaps (GstPad * pad, GstCaps * caps); +static gboolean gst_base_text_overlay_setcaps_txt (GstPad * pad, + GstCaps * caps); +static gboolean gst_base_text_overlay_src_event (GstPad * pad, + GstEvent * event); +static gboolean gst_base_text_overlay_src_query (GstPad * pad, + GstQuery * query); + +static gboolean gst_base_text_overlay_video_event (GstPad * pad, + GstEvent * event); +static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad, + GstBuffer * buffer); +static GstFlowReturn gst_base_text_overlay_video_bufferalloc (GstPad * pad, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buffer); + +static gboolean gst_base_text_overlay_text_event (GstPad * pad, + GstEvent * event); +static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad, + GstBuffer * buffer); +static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad, + GstPad * peer); +static void gst_base_text_overlay_text_pad_unlink (GstPad * pad); +static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay); +static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * + overlay); + +static void gst_base_text_overlay_finalize (GObject * object); +static void gst_base_text_overlay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_base_text_overlay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void +gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay, + PangoFontDescription * desc); + +GST_BOILERPLATE (GstBaseTextOverlay, gst_base_text_overlay, GstElement, + GST_TYPE_ELEMENT); + +static gchar * +gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay, + GstBuffer * video_frame) +{ + return g_strdup (overlay->default_text); +} + +static void +gst_base_text_overlay_base_init (gpointer g_class) +{ +} + +static void +gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + PangoFontMap *fontmap; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = gst_base_text_overlay_finalize; + gobject_class->set_property = gst_base_text_overlay_set_property; + gobject_class->get_property = gst_base_text_overlay_get_property; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_template_factory)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&video_sink_template_factory)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state); + + klass->pango_lock = g_mutex_new (); + + klass->get_text = gst_base_text_overlay_get_text; + fontmap = pango_cairo_font_map_get_default (); + klass->pango_context = + pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (fontmap)); + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT, + g_param_spec_string ("text", "text", + "Text to be display.", DEFAULT_PROP_TEXT, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING, + g_param_spec_boolean ("shaded-background", "shaded background", + "Whether to shade the background under the text area", + DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT, + g_param_spec_enum ("valignment", "vertical alignment", + "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN, + DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT, + g_param_spec_enum ("halignment", "horizontal alignment", + "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN, + DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGN, + g_param_spec_string ("valign", "vertical alignment", + "Vertical alignment of the text (deprecated; use valignment)", + DEFAULT_PROP_VALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGN, + g_param_spec_string ("halign", "horizontal alignment", + "Horizontal alignment of the text (deprecated; use halignment)", + DEFAULT_PROP_HALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD, + g_param_spec_int ("xpad", "horizontal paddding", + "Horizontal paddding when using left/right alignment", 0, G_MAXINT, + DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD, + g_param_spec_int ("ypad", "vertical padding", + "Vertical padding when using top/bottom alignment", 0, G_MAXINT, + DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_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, DEFAULT_PROP_DELTAX, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY, + g_param_spec_int ("deltay", "Y position modifier", + "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT, + DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstBaseTextOverlay:xpos + * + * Horizontal position of the rendered text when using positioned alignment. + * + * Since: 0.10.31 + **/ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS, + g_param_spec_double ("xpos", "horizontal position", + "Horizontal position when using position alignment", 0, 1.0, + DEFAULT_PROP_XPOS, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); + /** + * GstBaseTextOverlay:ypos + * + * Vertical position of the rendered text when using positioned alignment. + * + * Since: 0.10.31 + **/ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS, + g_param_spec_double ("ypos", "vertical position", + "Vertical position when using position alignment", 0, 1.0, + DEFAULT_PROP_YPOS, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE, + g_param_spec_enum ("wrap-mode", "wrap mode", + "Whether to wrap the text and if so how.", + GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC, + g_param_spec_string ("font-desc", "font description", + "Pango font description of font to be used for rendering. " + "See documentation of pango_font_description_from_string " + "for syntax.", DEFAULT_PROP_FONT_DESC, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + /** + * GstBaseTextOverlay:color + * + * Color of the rendered text. + * + * Since: 0.10.31 + **/ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR, + g_param_spec_uint ("color", "Color", + "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32, + DEFAULT_PROP_COLOR, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstBaseTextOverlay:line-alignment + * + * Alignment of text lines relative to each other (for multi-line text) + * + * Since: 0.10.15 + **/ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT, + g_param_spec_enum ("line-alignment", "line alignment", + "Alignment of text lines relative to each other.", + GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstBaseTextOverlay:silent + * + * If set, no text is rendered. Useful to switch off text rendering + * temporarily without removing the textoverlay element from the pipeline. + * + * Since: 0.10.15 + **/ + /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT, + g_param_spec_boolean ("silent", "silent", + "Whether to render the text string", + DEFAULT_PROP_SILENT, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); + /** + * GstBaseTextOverlay:wait-text + * + * If set, the video will block until a subtitle is received on the text pad. + * If video and subtitles are sent in sync, like from the same demuxer, this + * property should be set. + * + * Since: 0.10.20 + **/ + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT, + g_param_spec_boolean ("wait-text", "Wait Text", + "Whether to wait for subtitles", + DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize", + "Automatically adjust font size to screen-size.", + DEFAULT_PROP_AUTO_ADJUST_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER, + g_param_spec_boolean ("vertical-render", "vertical render", + "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_base_text_overlay_finalize (GObject * object) +{ + GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object); + + g_free (overlay->default_text); + + if (overlay->text_image) { + g_free (overlay->text_image); + overlay->text_image = NULL; + } + + if (overlay->layout) { + g_object_unref (overlay->layout); + overlay->layout = NULL; + } + + if (overlay->text_buffer) { + gst_buffer_unref (overlay->text_buffer); + overlay->text_buffer = NULL; + } + + if (overlay->cond) { + g_cond_free (overlay->cond); + overlay->cond = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_base_text_overlay_init (GstBaseTextOverlay * overlay, + GstBaseTextOverlayClass * klass) +{ + GstPadTemplate *template; + PangoFontDescription *desc; + + /* video sink */ + template = gst_static_pad_template_get (&video_sink_template_factory); + overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink"); + gst_object_unref (template); + gst_pad_set_getcaps_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_getcaps)); + gst_pad_set_setcaps_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_setcaps)); + gst_pad_set_event_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event)); + gst_pad_set_chain_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain)); + gst_pad_set_bufferalloc_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_bufferalloc)); + gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad); + + template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), + "text_sink"); + if (template) { + /* text sink */ + overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink"); + gst_object_unref (template); + + gst_pad_set_setcaps_function (overlay->text_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_setcaps_txt)); + gst_pad_set_event_function (overlay->text_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event)); + gst_pad_set_chain_function (overlay->text_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain)); + gst_pad_set_link_function (overlay->text_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link)); + gst_pad_set_unlink_function (overlay->text_sinkpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink)); + gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad); + } + + /* (video) source */ + template = gst_static_pad_template_get (&src_template_factory); + overlay->srcpad = gst_pad_new_from_template (template, "src"); + gst_object_unref (template); + gst_pad_set_getcaps_function (overlay->srcpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_getcaps)); + gst_pad_set_event_function (overlay->srcpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event)); + gst_pad_set_query_function (overlay->srcpad, + GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query)); + gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad); + + overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT; + overlay->layout = + pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS + (overlay)->pango_context); + desc = + pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS + (overlay)->pango_context); + gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc); + + overlay->color = DEFAULT_PROP_COLOR; + overlay->halign = DEFAULT_PROP_HALIGNMENT; + overlay->valign = DEFAULT_PROP_VALIGNMENT; + overlay->xpad = DEFAULT_PROP_XPAD; + overlay->ypad = DEFAULT_PROP_YPAD; + overlay->deltax = DEFAULT_PROP_DELTAX; + overlay->deltay = DEFAULT_PROP_DELTAY; + overlay->xpos = DEFAULT_PROP_XPOS; + overlay->ypos = DEFAULT_PROP_YPOS; + + overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE; + + overlay->want_shading = DEFAULT_PROP_SHADING; + overlay->shading_value = DEFAULT_SHADING_VALUE; + overlay->silent = DEFAULT_PROP_SILENT; + overlay->wait_text = DEFAULT_PROP_WAIT_TEXT; + overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE; + + overlay->default_text = g_strdup (DEFAULT_PROP_TEXT); + overlay->need_render = TRUE; + overlay->text_image = NULL; + overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER; + gst_base_text_overlay_update_render_mode (overlay); + + overlay->fps_n = 0; + overlay->fps_d = 1; + + overlay->text_buffer = NULL; + overlay->text_linked = FALSE; + overlay->cond = g_cond_new (); + gst_segment_init (&overlay->segment, GST_FORMAT_TIME); +} + +static void +gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay) +{ + if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) { + GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE"); + pango_layout_set_width (overlay->layout, -1); + } else { + int width; + + if (overlay->auto_adjust_size) { + width = DEFAULT_SCALE_BASIS * PANGO_SCALE; + if (overlay->use_vertical_render) { + width = width * (overlay->height - overlay->ypad * 2) / overlay->width; + } + } else { + width = + (overlay->use_vertical_render ? overlay->height : overlay->width) * + PANGO_SCALE; + } + + GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width); + GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode); + pango_layout_set_width (overlay->layout, width); + pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode); + } +} + +static void +gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay) +{ + PangoMatrix matrix = PANGO_MATRIX_INIT; + PangoContext *context = pango_layout_get_context (overlay->layout); + + if (overlay->use_vertical_render) { + pango_matrix_rotate (&matrix, -90); + pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO); + pango_context_set_matrix (context, &matrix); + pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT); + } else { + pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH); + pango_context_set_matrix (context, &matrix); + pango_layout_set_alignment (overlay->layout, overlay->line_align); + } +} + +static gboolean +gst_base_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps) +{ + GstBaseTextOverlay *overlay; + GstStructure *structure; + + overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + structure = gst_caps_get_structure (caps, 0); + overlay->have_pango_markup = + gst_structure_has_name (structure, "text/x-pango-markup"); + + gst_object_unref (overlay); + + return TRUE; +} + +/* FIXME: upstream nego (e.g. when the video window is resized) */ + +static gboolean +gst_base_text_overlay_setcaps (GstPad * pad, GstCaps * caps) +{ + GstBaseTextOverlay *overlay; + GstStructure *structure; + gboolean ret = FALSE; + const GValue *fps; + + if (!GST_PAD_IS_SINK (pad)) + return TRUE; + + g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); + + overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + overlay->width = 0; + overlay->height = 0; + structure = gst_caps_get_structure (caps, 0); + fps = gst_structure_get_value (structure, "framerate"); + + if (fps + && gst_video_format_parse_caps (caps, &overlay->format, &overlay->width, + &overlay->height)) { + ret = gst_pad_set_caps (overlay->srcpad, caps); + } + + overlay->fps_n = gst_value_get_fraction_numerator (fps); + overlay->fps_d = gst_value_get_fraction_denominator (fps); + + if (ret) { + GST_OBJECT_LOCK (overlay); + gst_base_text_overlay_update_wrap_mode (overlay); + GST_OBJECT_UNLOCK (overlay); + } + + gst_object_unref (overlay); + + return ret; +} + +static void +gst_base_text_overlay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object); + + GST_OBJECT_LOCK (overlay); + switch (prop_id) { + case PROP_TEXT: + g_free (overlay->default_text); + overlay->default_text = g_value_dup_string (value); + overlay->need_render = TRUE; + break; + case PROP_SHADING: + overlay->want_shading = g_value_get_boolean (value); + break; + case PROP_XPAD: + overlay->xpad = g_value_get_int (value); + break; + case PROP_YPAD: + overlay->ypad = g_value_get_int (value); + break; + case PROP_DELTAX: + overlay->deltax = g_value_get_int (value); + break; + case PROP_DELTAY: + overlay->deltay = g_value_get_int (value); + break; + case PROP_XPOS: + overlay->xpos = g_value_get_double (value); + break; + case PROP_YPOS: + overlay->ypos = g_value_get_double (value); + break; + case PROP_HALIGN:{ + const gchar *s = g_value_get_string (value); + + if (s && g_ascii_strcasecmp (s, "left") == 0) + overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_LEFT; + else if (s && g_ascii_strcasecmp (s, "center") == 0) + overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_CENTER; + else if (s && g_ascii_strcasecmp (s, "right") == 0) + overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT; + else + g_warning ("Invalid value '%s' for textoverlay property 'halign'", + GST_STR_NULL (s)); + break; + } + case PROP_VALIGN:{ + const gchar *s = g_value_get_string (value); + + if (s && g_ascii_strcasecmp (s, "baseline") == 0) + overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE; + else if (s && g_ascii_strcasecmp (s, "bottom") == 0) + overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM; + else if (s && g_ascii_strcasecmp (s, "top") == 0) + overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP; + else + g_warning ("Invalid value '%s' for textoverlay property 'valign'", + GST_STR_NULL (s)); + break; + } + case PROP_VALIGNMENT: + overlay->valign = g_value_get_enum (value); + break; + case PROP_HALIGNMENT: + overlay->halign = g_value_get_enum (value); + break; + case PROP_WRAP_MODE: + overlay->wrap_mode = g_value_get_enum (value); + gst_base_text_overlay_update_wrap_mode (overlay); + break; + case PROP_FONT_DESC: + { + PangoFontDescription *desc; + const gchar *fontdesc_str; + + fontdesc_str = g_value_get_string (value); + desc = pango_font_description_from_string (fontdesc_str); + if (desc) { + GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str); + pango_layout_set_font_description (overlay->layout, desc); + gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc); + pango_font_description_free (desc); + } else { + GST_WARNING_OBJECT (overlay, "font description parse failed: %s", + fontdesc_str); + } + break; + } + case PROP_COLOR: + overlay->color = g_value_get_uint (value); + break; + case PROP_SILENT: + overlay->silent = g_value_get_boolean (value); + break; + case PROP_LINE_ALIGNMENT: + overlay->line_align = g_value_get_enum (value); + pango_layout_set_alignment (overlay->layout, + (PangoAlignment) overlay->line_align); + break; + case PROP_WAIT_TEXT: + overlay->wait_text = g_value_get_boolean (value); + break; + case PROP_AUTO_ADJUST_SIZE: + overlay->auto_adjust_size = g_value_get_boolean (value); + overlay->need_render = TRUE; + break; + case PROP_VERTICAL_RENDER: + overlay->use_vertical_render = g_value_get_boolean (value); + gst_base_text_overlay_update_render_mode (overlay); + overlay->need_render = TRUE; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + overlay->need_render = TRUE; + GST_OBJECT_UNLOCK (overlay); +} + +static void +gst_base_text_overlay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object); + + GST_OBJECT_LOCK (overlay); + switch (prop_id) { + case PROP_TEXT: + g_value_set_string (value, overlay->default_text); + break; + case PROP_SHADING: + g_value_set_boolean (value, overlay->want_shading); + break; + case PROP_XPAD: + g_value_set_int (value, overlay->xpad); + break; + case PROP_YPAD: + g_value_set_int (value, overlay->ypad); + break; + case PROP_DELTAX: + g_value_set_int (value, overlay->deltax); + break; + case PROP_DELTAY: + g_value_set_int (value, overlay->deltay); + break; + case PROP_XPOS: + g_value_set_double (value, overlay->xpos); + break; + case PROP_YPOS: + g_value_set_double (value, overlay->ypos); + break; + case PROP_VALIGNMENT: + g_value_set_enum (value, overlay->valign); + break; + case PROP_HALIGNMENT: + g_value_set_enum (value, overlay->halign); + break; + case PROP_WRAP_MODE: + g_value_set_enum (value, overlay->wrap_mode); + break; + case PROP_SILENT: + g_value_set_boolean (value, overlay->silent); + break; + case PROP_LINE_ALIGNMENT: + g_value_set_enum (value, overlay->line_align); + break; + case PROP_WAIT_TEXT: + g_value_set_boolean (value, overlay->wait_text); + break; + case PROP_AUTO_ADJUST_SIZE: + g_value_set_boolean (value, overlay->auto_adjust_size); + break; + case PROP_VERTICAL_RENDER: + g_value_set_boolean (value, overlay->use_vertical_render); + break; + case PROP_COLOR: + g_value_set_uint (value, overlay->color); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + overlay->need_render = TRUE; + GST_OBJECT_UNLOCK (overlay); +} + +static gboolean +gst_base_text_overlay_src_query (GstPad * pad, GstQuery * query) +{ + gboolean ret = FALSE; + GstBaseTextOverlay *overlay = NULL; + + overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + ret = gst_pad_peer_query (overlay->video_sinkpad, query); + + gst_object_unref (overlay); + + return ret; +} + +static gboolean +gst_base_text_overlay_src_event (GstPad * pad, GstEvent * event) +{ + gboolean ret = FALSE; + GstBaseTextOverlay *overlay = NULL; + + overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK:{ + GstSeekFlags flags; + + /* We don't handle seek if we have not text pad */ + if (!overlay->text_linked) { + GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream"); + ret = gst_pad_push_event (overlay->video_sinkpad, event); + goto beach; + } + + GST_DEBUG_OBJECT (overlay, "seek received, driving from here"); + + gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL); + + /* Flush downstream, only for flushing seek */ + if (flags & GST_SEEK_FLAG_FLUSH) + gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ()); + + /* Mark ourself as flushing, unblock chains */ + GST_OBJECT_LOCK (overlay); + overlay->video_flushing = TRUE; + overlay->text_flushing = TRUE; + gst_base_text_overlay_pop_text (overlay); + GST_OBJECT_UNLOCK (overlay); + + /* Seek on each sink pad */ + gst_event_ref (event); + ret = gst_pad_push_event (overlay->video_sinkpad, event); + if (ret) { + ret = gst_pad_push_event (overlay->text_sinkpad, event); + } else { + gst_event_unref (event); + } + break; + } + default: + if (overlay->text_linked) { + gst_event_ref (event); + ret = gst_pad_push_event (overlay->video_sinkpad, event); + gst_pad_push_event (overlay->text_sinkpad, event); + } else { + ret = gst_pad_push_event (overlay->video_sinkpad, event); + } + break; + } + +beach: + gst_object_unref (overlay); + + return ret; +} + +static GstCaps * +gst_base_text_overlay_getcaps (GstPad * pad) +{ + GstBaseTextOverlay *overlay; + GstPad *otherpad; + GstCaps *caps; + + overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + 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; +} + +static void +gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay, + PangoFontDescription * desc) +{ + gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE; + overlay->shadow_offset = (double) (font_size) / 13.0; + overlay->outline_offset = (double) (font_size) / 15.0; + if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET) + overlay->outline_offset = MINIMUM_OUTLINE_OFFSET; +} + +#define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \ + b = (a > 0) ? MIN ((b * 255 + a / 2) / a, 255) : 0; \ + g = (a > 0) ? MIN ((g * 255 + a / 2) / a, 255) : 0; \ + r = (a > 0) ? MIN ((r * 255 + a / 2) / a, 255) : 0; \ +} G_STMT_END + +static inline void +gst_base_text_overlay_blit_1 (GstBaseTextOverlay * overlay, guchar * dest, + gint xpos, gint ypos, guchar * text_image, guint dest_stride) +{ + gint i, j = 0; + gint x, y; + guchar r, g, b, a; + guchar *pimage; + guchar *py; + gint width = overlay->image_width; + gint height = overlay->image_height; + + if (xpos < 0) { + xpos = 0; + } + + if (xpos + width > overlay->width) { + width = overlay->width - xpos; + } + + if (ypos + height > overlay->height) { + height = overlay->height - ypos; + } + + dest += (ypos / 1) * dest_stride; + + for (i = 0; i < height; i++) { + pimage = text_image + 4 * (i * overlay->image_width); + py = dest + i * dest_stride + xpos; + for (j = 0; j < width; j++) { + b = pimage[CAIRO_ARGB_B]; + g = pimage[CAIRO_ARGB_G]; + r = pimage[CAIRO_ARGB_R]; + a = pimage[CAIRO_ARGB_A]; + CAIRO_UNPREMULTIPLY (a, r, g, b); + + pimage += 4; + if (a == 0) { + py++; + continue; + } + COMP_Y (y, r, g, b); + x = *py; + BLEND (*py++, a, y, x); + } + } +} + +static inline void +gst_base_text_overlay_blit_sub2x2cbcr (GstBaseTextOverlay * overlay, + guchar * destcb, guchar * destcr, gint xpos, gint ypos, guchar * text_image, + guint destcb_stride, guint destcr_stride, guint pix_stride) +{ + gint i, j; + gint x, cb, cr; + gushort r, g, b, a; + gushort r1, g1, b1, a1; + guchar *pimage1, *pimage2; + guchar *pcb, *pcr; + gint width = overlay->image_width - 2; + gint height = overlay->image_height - 2; + + xpos *= pix_stride; + + if (xpos < 0) { + xpos = 0; + } + + if (xpos + width > overlay->width) { + width = overlay->width - xpos; + } + + if (ypos + height > overlay->height) { + height = overlay->height - ypos; + } + + destcb += (ypos / 2) * destcb_stride; + destcr += (ypos / 2) * destcr_stride; + + for (i = 0; i < height; i += 2) { + pimage1 = text_image + 4 * (i * overlay->image_width); + pimage2 = pimage1 + 4 * overlay->image_width; + pcb = destcb + (i / 2) * destcb_stride + xpos / 2; + pcr = destcr + (i / 2) * destcr_stride + xpos / 2; + for (j = 0; j < width; j += 2) { + b = pimage1[CAIRO_ARGB_B]; + g = pimage1[CAIRO_ARGB_G]; + r = pimage1[CAIRO_ARGB_R]; + a = pimage1[CAIRO_ARGB_A]; + CAIRO_UNPREMULTIPLY (a, r, g, b); + pimage1 += 4; + + b1 = pimage1[CAIRO_ARGB_B]; + g1 = pimage1[CAIRO_ARGB_G]; + r1 = pimage1[CAIRO_ARGB_R]; + a1 = pimage1[CAIRO_ARGB_A]; + CAIRO_UNPREMULTIPLY (a1, r1, g1, b1); + b += b1; + g += g1; + r += r1; + a += a1; + pimage1 += 4; + + b1 = pimage2[CAIRO_ARGB_B]; + g1 = pimage2[CAIRO_ARGB_G]; + r1 = pimage2[CAIRO_ARGB_R]; + a1 = pimage2[CAIRO_ARGB_A]; + CAIRO_UNPREMULTIPLY (a1, r1, g1, b1); + b += b1; + g += g1; + r += r1; + a += a1; + pimage2 += 4; + + /* + 2 for rounding */ + b1 = pimage2[CAIRO_ARGB_B]; + g1 = pimage2[CAIRO_ARGB_G]; + r1 = pimage2[CAIRO_ARGB_R]; + a1 = pimage2[CAIRO_ARGB_A]; + CAIRO_UNPREMULTIPLY (a1, r1, g1, b1); + b += b1 + 2; + g += g1 + 2; + r += r1 + 2; + a += a1 + 2; + pimage2 += 4; + + b /= 4; + g /= 4; + r /= 4; + a /= 4; + + if (a == 0) { + pcb += pix_stride; + pcr += pix_stride; + continue; + } + COMP_U (cb, r, g, b); + COMP_V (cr, r, g, b); + + x = *pcb; + BLEND (*pcb, a, cb, x); + x = *pcr; + BLEND (*pcr, a, cr, x); + + pcb += pix_stride; + pcr += pix_stride; + } + } +} + +static void +gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay, + const gchar * string, gint textlen) +{ + cairo_t *cr; + cairo_surface_t *surface; + PangoRectangle ink_rect, logical_rect; + cairo_matrix_t cairo_matrix; + int width, height; + double scalef = 1.0; + double a, r, g, b; + + g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock); + + if (overlay->auto_adjust_size) { + /* 640 pixel is default */ + scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS; + } + pango_layout_set_width (overlay->layout, -1); + /* set text on pango layout */ + pango_layout_set_markup (overlay->layout, string, textlen); + + /* get subtitle image size */ + pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect); + + width = (logical_rect.width + overlay->shadow_offset) * scalef; + + if (width + overlay->deltax > + (overlay->use_vertical_render ? overlay->height : overlay->width)) { + /* + * subtitle image width is larger then overlay width + * so rearrange overlay wrap mode. + */ + gst_base_text_overlay_update_wrap_mode (overlay); + pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect); + width = overlay->width; + } + + height = + (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef; + if (height > overlay->height) { + height = overlay->height; + } + if (overlay->use_vertical_render) { + PangoRectangle rect; + PangoContext *context; + PangoMatrix matrix = PANGO_MATRIX_INIT; + int tmp; + + context = pango_layout_get_context (overlay->layout); + + pango_matrix_rotate (&matrix, -90); + + rect.x = rect.y = 0; + rect.width = width; + rect.height = height; + pango_matrix_transform_pixel_rectangle (&matrix, &rect); + matrix.x0 = -rect.x; + matrix.y0 = -rect.y; + + pango_context_set_matrix (context, &matrix); + + cairo_matrix.xx = matrix.xx; + cairo_matrix.yx = matrix.yx; + cairo_matrix.xy = matrix.xy; + cairo_matrix.yy = matrix.yy; + cairo_matrix.x0 = matrix.x0; + cairo_matrix.y0 = matrix.y0; + cairo_matrix_scale (&cairo_matrix, scalef, scalef); + + tmp = height; + height = width; + width = tmp; + } else { + cairo_matrix_init_scale (&cairo_matrix, scalef, scalef); + } + + g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock); + + /* reallocate surface */ + overlay->text_image = g_realloc (overlay->text_image, 4 * width * height); + + surface = cairo_image_surface_create_for_data (overlay->text_image, + CAIRO_FORMAT_ARGB32, width, height, width * 4); + cr = cairo_create (surface); + + /* clear surface */ + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + + if (overlay->want_shading) + cairo_paint_with_alpha (cr, overlay->shading_value); + + /* apply transformations */ + cairo_set_matrix (cr, &cairo_matrix); + + /* FIXME: We use show_layout everywhere except for the surface + * because it's really faster and internally does all kinds of + * caching. Unfortunately we have to paint to a cairo path for + * the outline and this is slow. Once Pango supports user fonts + * we should use them, see + * https://bugzilla.gnome.org/show_bug.cgi?id=598695 + * + * Idea would the be, to create a cairo user font that + * does shadow, outline, text painting in the + * render_glyph function. + */ + + /* draw shadow text */ + cairo_save (cr); + cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5); + pango_cairo_show_layout (cr, overlay->layout); + cairo_restore (cr); + + /* draw outline text */ + cairo_save (cr); + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_set_line_width (cr, overlay->outline_offset); + pango_cairo_layout_path (cr, overlay->layout); + cairo_stroke (cr); + cairo_restore (cr); + + a = (overlay->color >> 24) & 0xff; + r = (overlay->color >> 16) & 0xff; + g = (overlay->color >> 8) & 0xff; + b = (overlay->color >> 0) & 0xff; + + /* draw text */ + cairo_save (cr); + cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0); + pango_cairo_show_layout (cr, overlay->layout); + cairo_restore (cr); + + cairo_destroy (cr); + cairo_surface_destroy (surface); + overlay->image_width = width; + overlay->image_height = height; + overlay->baseline_y = ink_rect.y; +} + +#define BOX_XPAD 6 +#define BOX_YPAD 6 + +static inline void +gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay, + guchar * dest, gint x0, gint x1, gint y0, gint y1) +{ + gint i, j, dest_stride; + + dest_stride = gst_video_format_get_row_stride (overlay->format, 0, + overlay->width); + + x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width); + x1 = CLAMP (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] + overlay->shading_value; + + dest[(i * dest_stride) + j] = CLAMP (y, 0, 255); + } + } +} + +static inline void +gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay, + guchar * dest, gint x0, gint x1, gint y0, gint y1) +{ + gint i, j; + guint dest_stride, pixel_stride, component_offset; + + dest_stride = gst_video_format_get_row_stride (overlay->format, 0, + overlay->width); + pixel_stride = gst_video_format_get_pixel_stride (overlay->format, 0); + component_offset = + gst_video_format_get_component_offset (overlay->format, 0, overlay->width, + overlay->height); + + x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width); + x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width); + + y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height); + y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height); + + if (x0 != 0) + x0 = gst_video_format_get_component_width (overlay->format, 0, x0); + if (x1 != 0) + x1 = gst_video_format_get_component_width (overlay->format, 0, x1); + + if (y0 != 0) + y0 = gst_video_format_get_component_height (overlay->format, 0, y0); + if (y1 != 0) + y1 = gst_video_format_get_component_height (overlay->format, 0, y1); + + for (i = y0; i < y1; i++) { + for (j = x0; j < x1; j++) { + gint y; + gint y_pos; + + y_pos = (i * dest_stride) + j * pixel_stride + component_offset; + y = dest[y_pos] + overlay->shading_value; + + dest[y_pos] = CLAMP (y, 0, 255); + } + } +} + +#define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB +#define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB +#define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB +static inline void +gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay, guchar * dest, + gint x0, gint x1, gint y0, gint y1) +{ + gint i, j; + + x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width); + x1 = CLAMP (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, y_pos, k; + + y_pos = (i * 4 * overlay->width) + j * 4; + for (k = 0; k < 4; k++) { + y = dest[y_pos + k] + overlay->shading_value; + dest[y_pos + k] = CLAMP (y, 0, 255); + } + } + } +} + +#define ARGB_SHADE_FUNCTION(name, OFFSET) \ +static inline void \ +gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, guchar * dest, \ +gint x0, gint x1, gint y0, gint y1) \ +{ \ + gint i, j;\ + \ + x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\ + x1 = CLAMP (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, y_pos, k;\ + y_pos = (i * 4 * overlay->width) + j * 4;\ + for (k = OFFSET; k < 3+OFFSET; k++) {\ + y = dest[y_pos + k] + overlay->shading_value;\ + dest[y_pos + k] = CLAMP (y, 0, 255);\ + }\ + }\ + }\ +} +ARGB_SHADE_FUNCTION (ARGB, 1); +ARGB_SHADE_FUNCTION (ABGR, 1); +ARGB_SHADE_FUNCTION (RGBA, 0); +ARGB_SHADE_FUNCTION (BGRA, 0); + + +/* FIXME: + * - use proper strides and offset for I420 + * - don't draw over the edge of the picture (try a longer + * text with a huge font size) + */ + +static inline void +gst_base_text_overlay_blit_NV12_NV21 (GstBaseTextOverlay * overlay, + guint8 * yuv_pixels, gint xpos, gint ypos) +{ + int y_stride, uv_stride; + int u_offset, v_offset; + int h, w; + + /* because U/V is 2x2 subsampled, we need to round, either up or down, + * to a boundary of integer number of U/V pixels: + */ + xpos = GST_ROUND_UP_2 (xpos); + ypos = GST_ROUND_UP_2 (ypos); + + w = overlay->width; + h = overlay->height; + + y_stride = gst_video_format_get_row_stride (overlay->format, 0, w); + uv_stride = gst_video_format_get_row_stride (overlay->format, 1, w); + u_offset = gst_video_format_get_component_offset (overlay->format, 1, w, h); + v_offset = gst_video_format_get_component_offset (overlay->format, 2, w, h); + + gst_base_text_overlay_blit_1 (overlay, yuv_pixels, xpos, ypos, + overlay->text_image, y_stride); + gst_base_text_overlay_blit_sub2x2cbcr (overlay, yuv_pixels + u_offset, + yuv_pixels + v_offset, xpos, ypos, overlay->text_image, uv_stride, + uv_stride, 2); +} + +static inline void +gst_base_text_overlay_blit_I420 (GstBaseTextOverlay * overlay, + guint8 * yuv_pixels, gint xpos, gint ypos) +{ + int y_stride, u_stride, v_stride; + int u_offset, v_offset; + int h, w; + + /* because U/V is 2x2 subsampled, we need to round, either up or down, + * to a boundary of integer number of U/V pixels: + */ + xpos = GST_ROUND_UP_2 (xpos); + ypos = GST_ROUND_UP_2 (ypos); + + w = overlay->width; + h = overlay->height; + + y_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0, w); + u_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1, w); + v_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 2, w); + u_offset = + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 1, w, h); + v_offset = + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 2, w, h); + + gst_base_text_overlay_blit_1 (overlay, yuv_pixels, xpos, ypos, + overlay->text_image, y_stride); + gst_base_text_overlay_blit_sub2x2cbcr (overlay, yuv_pixels + u_offset, + yuv_pixels + v_offset, xpos, ypos, overlay->text_image, u_stride, + v_stride, 1); +} + +static inline void +gst_base_text_overlay_blit_UYVY (GstBaseTextOverlay * overlay, + guint8 * yuv_pixels, gint xpos, gint ypos) +{ + int a0, r0, g0, b0; + int a1, r1, g1, b1; + int y0, y1, u, v; + int i, j; + int h, w; + guchar *pimage, *dest; + + /* because U/V is 2x horizontally subsampled, we need to round to a + * boundary of integer number of U/V pixels in x dimension: + */ + xpos = GST_ROUND_UP_2 (xpos); + + w = overlay->image_width - 2; + h = overlay->image_height - 2; + + if (xpos < 0) { + xpos = 0; + } + + if (xpos + w > overlay->width) { + w = overlay->width - xpos; + } + + if (ypos + h > overlay->height) { + h = overlay->height - ypos; + } + + for (i = 0; i < h; i++) { + pimage = overlay->text_image + i * overlay->image_width * 4; + dest = yuv_pixels + (i + ypos) * overlay->width * 2 + xpos * 2; + for (j = 0; j < w; j += 2) { + b0 = pimage[CAIRO_ARGB_B]; + g0 = pimage[CAIRO_ARGB_G]; + r0 = pimage[CAIRO_ARGB_R]; + a0 = pimage[CAIRO_ARGB_A]; + CAIRO_UNPREMULTIPLY (a0, r0, g0, b0); + pimage += 4; + + b1 = pimage[CAIRO_ARGB_B]; + g1 = pimage[CAIRO_ARGB_G]; + r1 = pimage[CAIRO_ARGB_R]; + a1 = pimage[CAIRO_ARGB_A]; + CAIRO_UNPREMULTIPLY (a1, r1, g1, b1); + pimage += 4; + + a0 += a1 + 2; + a0 /= 2; + if (a0 == 0) { + dest += 4; + continue; + } + + COMP_Y (y0, r0, g0, b0); + COMP_Y (y1, r1, g1, b1); + + b0 += b1 + 2; + g0 += g1 + 2; + r0 += r1 + 2; + + b0 /= 2; + g0 /= 2; + r0 /= 2; + + COMP_U (u, r0, g0, b0); + COMP_V (v, r0, g0, b0); + + BLEND (*dest, a0, u, *dest); + dest++; + BLEND (*dest, a0, y0, *dest); + dest++; + BLEND (*dest, a0, v, *dest); + dest++; + BLEND (*dest, a0, y1, *dest); + dest++; + } + } +} + +static inline void +gst_base_text_overlay_blit_AYUV (GstBaseTextOverlay * overlay, + guint8 * rgb_pixels, gint xpos, gint ypos) +{ + int a, r, g, b, a1; + int y, u, v; + int i, j; + int h, w; + guchar *pimage, *dest; + + w = overlay->image_width; + h = overlay->image_height; + + if (xpos < 0) { + xpos = 0; + } + + if (xpos + w > overlay->width) { + w = overlay->width - xpos; + } + + if (ypos + h > overlay->height) { + h = overlay->height - ypos; + } + + for (i = 0; i < h; i++) { + pimage = overlay->text_image + i * overlay->image_width * 4; + dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; + for (j = 0; j < w; j++) { + a = pimage[CAIRO_ARGB_A]; + b = pimage[CAIRO_ARGB_B]; + g = pimage[CAIRO_ARGB_G]; + r = pimage[CAIRO_ARGB_R]; + + CAIRO_UNPREMULTIPLY (a, r, g, b); + + // convert background to yuv + COMP_Y (y, r, g, b); + COMP_U (u, r, g, b); + COMP_V (v, r, g, b); + + // preform text "OVER" background alpha compositing + a1 = a + (dest[0] * (255 - a)) / 255 + 1; // add 1 to prevent divide by 0 + OVER (dest[1], a, y, dest[0], dest[1], a1); + OVER (dest[2], a, u, dest[0], dest[2], a1); + OVER (dest[3], a, v, dest[0], dest[3], a1); + dest[0] = a1 - 1; // remove the temporary 1 we added + + pimage += 4; + dest += 4; + } + } +} + +#define xRGB_BLIT_FUNCTION(name, R, G, B) \ +static inline void \ +gst_base_text_overlay_blit_##name (GstBaseTextOverlay * overlay, \ + guint8 * rgb_pixels, gint xpos, gint ypos) \ +{ \ + int a, r, g, b; \ + int i, j; \ + int h, w; \ + guchar *pimage, *dest; \ + \ + w = overlay->image_width; \ + h = overlay->image_height; \ + \ + if (xpos < 0) { \ + xpos = 0; \ + } \ + \ + if (xpos + w > overlay->width) { \ + w = overlay->width - xpos; \ + } \ + \ + if (ypos + h > overlay->height) { \ + h = overlay->height - ypos; \ + } \ + \ + for (i = 0; i < h; i++) { \ + pimage = overlay->text_image + i * overlay->image_width * 4; \ + dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; \ + for (j = 0; j < w; j++) { \ + a = pimage[CAIRO_ARGB_A]; \ + b = pimage[CAIRO_ARGB_B]; \ + g = pimage[CAIRO_ARGB_G]; \ + r = pimage[CAIRO_ARGB_R]; \ + CAIRO_UNPREMULTIPLY (a, r, g, b); \ + b = (b*a + dest[B] * (255-a)) / 255; \ + g = (g*a + dest[G] * (255-a)) / 255; \ + r = (r*a + dest[R] * (255-a)) / 255; \ + \ + dest[B] = b; \ + dest[G] = g; \ + dest[R] = r; \ + pimage += 4; \ + dest += 4; \ + } \ + } \ +} +xRGB_BLIT_FUNCTION (xRGB, 1, 2, 3); +xRGB_BLIT_FUNCTION (BGRx, 2, 1, 0); +xRGB_BLIT_FUNCTION (xBGR, 3, 2, 1); +xRGB_BLIT_FUNCTION (RGBx, 0, 1, 2); + +#define ARGB_BLIT_FUNCTION(name, A, R, G, B) \ +static inline void \ +gst_base_text_overlay_blit_##name (GstBaseTextOverlay * overlay, \ + guint8 * rgb_pixels, gint xpos, gint ypos) \ +{ \ + int a, r, g, b, a1; \ + int i, j; \ + int h, w; \ + guchar *pimage, *dest; \ + \ + w = overlay->image_width; \ + h = overlay->image_height; \ + \ + if (xpos < 0) { \ + xpos = 0; \ + } \ + \ + if (xpos + w > overlay->width) { \ + w = overlay->width - xpos; \ + } \ + \ + if (ypos + h > overlay->height) { \ + h = overlay->height - ypos; \ + } \ + \ + for (i = 0; i < h; i++) { \ + pimage = overlay->text_image + i * overlay->image_width * 4; \ + dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; \ + for (j = 0; j < w; j++) { \ + a = pimage[CAIRO_ARGB_A]; \ + b = pimage[CAIRO_ARGB_B]; \ + g = pimage[CAIRO_ARGB_G]; \ + r = pimage[CAIRO_ARGB_R]; \ + CAIRO_UNPREMULTIPLY (a, r, g, b); \ + a1 = a + (dest[A] * (255 - a)) / 255 + 1; \ + OVER (dest[R], a, r, dest[0], dest[R], a1); \ + OVER (dest[G], a, g, dest[0], dest[G], a1); \ + OVER (dest[B], a, b, dest[0], dest[B], a1); \ + dest[A] = a1 - 1; \ + pimage += 4; \ + dest += 4; \ + } \ + } \ +} +ARGB_BLIT_FUNCTION (RGBA, 3, 0, 1, 2); +ARGB_BLIT_FUNCTION (BGRA, 3, 2, 1, 0); +ARGB_BLIT_FUNCTION (ARGB, 0, 1, 2, 3); +ARGB_BLIT_FUNCTION (ABGR, 0, 3, 2, 1); + +static void +gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay, + const gchar * text, gint textlen) +{ + gchar *string; + + if (!overlay->need_render) { + GST_DEBUG ("Using previously rendered text."); + return; + } + + /* -1 is the whole string */ + if (text != NULL && textlen < 0) { + textlen = strlen (text); + } + + if (text != NULL) { + string = g_strndup (text, textlen); + } else { /* empty string */ + string = g_strdup (" "); + } + g_strdelimit (string, "\r\t", ' '); + textlen = strlen (string); + + /* FIXME: should we check for UTF-8 here? */ + + GST_DEBUG ("Rendering '%s'", string); + gst_base_text_overlay_render_pangocairo (overlay, string, textlen); + + g_free (string); + + overlay->need_render = FALSE; +} + +static GstFlowReturn +gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay, + GstBuffer * video_frame) +{ + gint xpos, ypos; + gint width, height; + GstBaseTextOverlayVAlign valign; + GstBaseTextOverlayHAlign halign; + guint8 *data; + gsize size; + + width = overlay->image_width; + height = overlay->image_height; + + video_frame = gst_buffer_make_writable (video_frame); + + data = gst_buffer_map (video_frame, &size, NULL, GST_MAP_WRITE); + + if (overlay->use_vertical_render) + halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT; + else + halign = overlay->halign; + + switch (halign) { + case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT: + xpos = overlay->xpad; + break; + case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER: + xpos = (overlay->width - width) / 2; + break; + case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT: + xpos = overlay->width - width - overlay->xpad; + break; + case GST_BASE_TEXT_OVERLAY_HALIGN_POS: + xpos = (gint) (overlay->width * overlay->xpos) - width / 2; + xpos = CLAMP (xpos, 0, overlay->width - width); + if (xpos < 0) + xpos = 0; + break; + default: + xpos = 0; + } + xpos += overlay->deltax; + + if (overlay->use_vertical_render) + valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP; + else + valign = overlay->valign; + + switch (valign) { + case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM: + ypos = overlay->height - height - overlay->ypad; + break; + case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE: + ypos = overlay->height - (height + overlay->ypad); + break; + case GST_BASE_TEXT_OVERLAY_VALIGN_TOP: + ypos = overlay->ypad; + break; + case GST_BASE_TEXT_OVERLAY_VALIGN_POS: + ypos = (gint) (overlay->height * overlay->ypos) - height / 2; + ypos = CLAMP (ypos, 0, overlay->height - height); + break; + case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER: + ypos = (overlay->height - height) / 2; + break; + default: + ypos = overlay->ypad; + break; + } + ypos += overlay->deltay; + + /* shaded background box */ + if (overlay->want_shading) { + switch (overlay->format) { + case GST_VIDEO_FORMAT_I420: + case GST_VIDEO_FORMAT_NV12: + case GST_VIDEO_FORMAT_NV21: + gst_base_text_overlay_shade_planar_Y (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_AYUV: + case GST_VIDEO_FORMAT_UYVY: + gst_base_text_overlay_shade_packed_Y (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_xRGB: + gst_base_text_overlay_shade_xRGB (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_xBGR: + gst_base_text_overlay_shade_xBGR (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_BGRx: + gst_base_text_overlay_shade_BGRx (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_RGBx: + gst_base_text_overlay_shade_RGBx (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_ARGB: + gst_base_text_overlay_shade_ARGB (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_ABGR: + gst_base_text_overlay_shade_ABGR (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_RGBA: + gst_base_text_overlay_shade_RGBA (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + case GST_VIDEO_FORMAT_BGRA: + gst_base_text_overlay_shade_BGRA (overlay, data, + xpos, xpos + overlay->image_width, + ypos, ypos + overlay->image_height); + break; + default: + g_assert_not_reached (); + } + } + + if (ypos < 0) + ypos = 0; + + if (overlay->text_image) { + switch (overlay->format) { + case GST_VIDEO_FORMAT_I420: + gst_base_text_overlay_blit_I420 (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_NV12: + case GST_VIDEO_FORMAT_NV21: + gst_base_text_overlay_blit_NV12_NV21 (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_UYVY: + gst_base_text_overlay_blit_UYVY (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_AYUV: + gst_base_text_overlay_blit_AYUV (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_BGRx: + gst_base_text_overlay_blit_BGRx (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_xRGB: + gst_base_text_overlay_blit_xRGB (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_RGBx: + gst_base_text_overlay_blit_RGBx (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_xBGR: + gst_base_text_overlay_blit_xBGR (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_ARGB: + gst_base_text_overlay_blit_ARGB (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_ABGR: + gst_base_text_overlay_blit_ABGR (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_RGBA: + gst_base_text_overlay_blit_RGBA (overlay, data, xpos, ypos); + break; + case GST_VIDEO_FORMAT_BGRA: + gst_base_text_overlay_blit_BGRA (overlay, data, xpos, ypos); + break; + default: + g_assert_not_reached (); + } + } + gst_buffer_unmap (video_frame, data, size); + + return gst_pad_push (overlay->srcpad, video_frame); +} + +static GstPadLinkReturn +gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer) +{ + GstBaseTextOverlay *overlay; + + overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (overlay, "Text pad linked"); + + overlay->text_linked = TRUE; + + gst_object_unref (overlay); + + return GST_PAD_LINK_OK; +} + +static void +gst_base_text_overlay_text_pad_unlink (GstPad * pad) +{ + GstBaseTextOverlay *overlay; + + /* don't use gst_pad_get_parent() here, will deadlock */ + overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad)); + + GST_DEBUG_OBJECT (overlay, "Text pad unlinked"); + + overlay->text_linked = FALSE; + + gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED); +} + +static gboolean +gst_base_text_overlay_text_event (GstPad * pad, GstEvent * event) +{ + gboolean ret = FALSE; + GstBaseTextOverlay *overlay = NULL; + + overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT:{ + GstFormat fmt; + gboolean update; + gdouble rate, applied_rate; + gint64 cur, stop, time; + + overlay->text_eos = FALSE; + + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &fmt, &cur, &stop, &time); + + if (fmt == GST_FORMAT_TIME) { + GST_OBJECT_LOCK (overlay); + gst_segment_set_newsegment_full (&overlay->text_segment, update, rate, + applied_rate, GST_FORMAT_TIME, cur, stop, time); + GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT, + &overlay->text_segment); + GST_OBJECT_UNLOCK (overlay); + } else { + GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL), + ("received non-TIME newsegment event on text input")); + } + + gst_event_unref (event); + ret = TRUE; + + /* wake up the video chain, it might be waiting for a text buffer or + * a text segment update */ + GST_OBJECT_LOCK (overlay); + GST_BASE_TEXT_OVERLAY_BROADCAST (overlay); + GST_OBJECT_UNLOCK (overlay); + break; + } + case GST_EVENT_FLUSH_STOP: + GST_OBJECT_LOCK (overlay); + GST_INFO_OBJECT (overlay, "text flush stop"); + overlay->text_flushing = FALSE; + overlay->text_eos = FALSE; + gst_base_text_overlay_pop_text (overlay); + gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME); + GST_OBJECT_UNLOCK (overlay); + gst_event_unref (event); + ret = TRUE; + break; + case GST_EVENT_FLUSH_START: + GST_OBJECT_LOCK (overlay); + GST_INFO_OBJECT (overlay, "text flush start"); + overlay->text_flushing = TRUE; + GST_BASE_TEXT_OVERLAY_BROADCAST (overlay); + GST_OBJECT_UNLOCK (overlay); + gst_event_unref (event); + ret = TRUE; + break; + case GST_EVENT_EOS: + GST_OBJECT_LOCK (overlay); + overlay->text_eos = TRUE; + GST_INFO_OBJECT (overlay, "text EOS"); + /* wake up the video chain, it might be waiting for a text buffer or + * a text segment update */ + GST_BASE_TEXT_OVERLAY_BROADCAST (overlay); + GST_OBJECT_UNLOCK (overlay); + gst_event_unref (event); + ret = TRUE; + break; + default: + ret = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (overlay); + + return ret; +} + +static gboolean +gst_base_text_overlay_video_event (GstPad * pad, GstEvent * event) +{ + gboolean ret = FALSE; + GstBaseTextOverlay *overlay = NULL; + + overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + GstFormat format; + gdouble rate; + gint64 start, stop, time; + gboolean update; + + GST_DEBUG_OBJECT (overlay, "received new segment"); + + gst_event_parse_new_segment (event, &update, &rate, &format, &start, + &stop, &time); + + if (format == GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT, + &overlay->segment); + + gst_segment_set_newsegment (&overlay->segment, update, rate, format, + start, stop, time); + } else { + GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL), + ("received non-TIME newsegment event on video input")); + } + + ret = gst_pad_event_default (pad, event); + break; + } + case GST_EVENT_EOS: + GST_OBJECT_LOCK (overlay); + GST_INFO_OBJECT (overlay, "video EOS"); + overlay->video_eos = TRUE; + GST_OBJECT_UNLOCK (overlay); + ret = gst_pad_event_default (pad, event); + break; + case GST_EVENT_FLUSH_START: + GST_OBJECT_LOCK (overlay); + GST_INFO_OBJECT (overlay, "video flush start"); + overlay->video_flushing = TRUE; + GST_BASE_TEXT_OVERLAY_BROADCAST (overlay); + GST_OBJECT_UNLOCK (overlay); + ret = gst_pad_event_default (pad, event); + break; + case GST_EVENT_FLUSH_STOP: + GST_OBJECT_LOCK (overlay); + GST_INFO_OBJECT (overlay, "video flush stop"); + overlay->video_flushing = FALSE; + overlay->video_eos = FALSE; + gst_segment_init (&overlay->segment, GST_FORMAT_TIME); + GST_OBJECT_UNLOCK (overlay); + ret = gst_pad_event_default (pad, event); + break; + default: + ret = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (overlay); + + return ret; +} + +static GstFlowReturn +gst_base_text_overlay_video_bufferalloc (GstPad * pad, guint64 offset, + guint size, GstCaps * caps, GstBuffer ** buffer) +{ + GstBaseTextOverlay *overlay = + GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + GstFlowReturn ret = GST_FLOW_WRONG_STATE; + GstPad *allocpad; + + GST_OBJECT_LOCK (overlay); + allocpad = overlay->srcpad ? gst_object_ref (overlay->srcpad) : NULL; + GST_OBJECT_UNLOCK (overlay); + + if (allocpad) { + ret = gst_pad_alloc_buffer (allocpad, offset, size, caps, buffer); + gst_object_unref (allocpad); + } + + gst_object_unref (overlay); + return ret; +} + +/* Called with lock held */ +static void +gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay) +{ + g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay)); + + if (overlay->text_buffer) { + GST_DEBUG_OBJECT (overlay, "releasing text buffer %p", + overlay->text_buffer); + gst_buffer_unref (overlay->text_buffer); + overlay->text_buffer = NULL; + } + + /* Let the text task know we used that buffer */ + GST_BASE_TEXT_OVERLAY_BROADCAST (overlay); +} + +/* We receive text buffers here. If they are out of segment we just ignore them. + If the buffer is in our segment we keep it internally except if another one + is already waiting here, in that case we wait that it gets kicked out */ +static GstFlowReturn +gst_base_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstBaseTextOverlay *overlay = NULL; + gboolean in_seg = FALSE; + gint64 clip_start = 0, clip_stop = 0; + + overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad)); + + GST_OBJECT_LOCK (overlay); + + if (overlay->text_flushing) { + GST_OBJECT_UNLOCK (overlay); + ret = GST_FLOW_WRONG_STATE; + GST_LOG_OBJECT (overlay, "text flushing"); + goto beach; + } + + if (overlay->text_eos) { + GST_OBJECT_UNLOCK (overlay); + ret = GST_FLOW_UNEXPECTED; + GST_LOG_OBJECT (overlay, "text EOS"); + goto beach; + } + + GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%" + GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) + + GST_BUFFER_DURATION (buffer))); + + if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) { + GstClockTime stop; + + if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer))) + stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer); + else + stop = GST_CLOCK_TIME_NONE; + + in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop); + } else { + in_seg = TRUE; + } + + if (in_seg) { + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + GST_BUFFER_TIMESTAMP (buffer) = clip_start; + else if (GST_BUFFER_DURATION_IS_VALID (buffer)) + GST_BUFFER_DURATION (buffer) = clip_stop - clip_start; + + /* Wait for the previous buffer to go away */ + while (overlay->text_buffer != NULL) { + GST_DEBUG ("Pad %s:%s has a buffer queued, waiting", + GST_DEBUG_PAD_NAME (pad)); + GST_BASE_TEXT_OVERLAY_WAIT (overlay); + GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad)); + if (overlay->text_flushing) { + GST_OBJECT_UNLOCK (overlay); + ret = GST_FLOW_WRONG_STATE; + goto beach; + } + } + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + gst_segment_set_last_stop (&overlay->text_segment, GST_FORMAT_TIME, + clip_start); + + overlay->text_buffer = buffer; + /* That's a new text buffer we need to render */ + overlay->need_render = TRUE; + + /* in case the video chain is waiting for a text buffer, wake it up */ + GST_BASE_TEXT_OVERLAY_BROADCAST (overlay); + } + + GST_OBJECT_UNLOCK (overlay); + +beach: + + return ret; +} + +static GstFlowReturn +gst_base_text_overlay_video_chain (GstPad * pad, GstBuffer * buffer) +{ + GstBaseTextOverlayClass *klass; + GstBaseTextOverlay *overlay; + GstFlowReturn ret = GST_FLOW_OK; + gboolean in_seg = FALSE; + gint64 start, stop, clip_start = 0, clip_stop = 0; + gchar *text = NULL; + + overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad)); + klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay); + + if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + goto missing_timestamp; + + /* ignore buffers that are outside of the current segment */ + start = GST_BUFFER_TIMESTAMP (buffer); + + if (!GST_BUFFER_DURATION_IS_VALID (buffer)) { + stop = GST_CLOCK_TIME_NONE; + } else { + stop = start + GST_BUFFER_DURATION (buffer); + } + + GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%" + GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + + /* segment_clip() will adjust start unconditionally to segment_start if + * no stop time is provided, so handle this ourselves */ + if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start) + goto out_of_segment; + + in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop, + &clip_start, &clip_stop); + + if (!in_seg) + goto out_of_segment; + + /* if the buffer is only partially in the segment, fix up stamps */ + if (clip_start != start || (stop != -1 && clip_stop != stop)) { + GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment"); + buffer = gst_buffer_make_writable (buffer); + GST_BUFFER_TIMESTAMP (buffer) = clip_start; + if (stop != -1) + GST_BUFFER_DURATION (buffer) = clip_stop - clip_start; + } + + /* now, after we've done the clipping, fix up end time if there's no + * duration (we only use those estimated values internally though, we + * don't want to set bogus values on the buffer itself) */ + if (stop == -1) { + GstStructure *s; + gint fps_num, fps_denom; + + s = gst_caps_get_structure (GST_PAD_CAPS (pad), 0); + if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) && + fps_num && fps_denom) { + GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate"); + stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num); + } else { + GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration"); + stop = start + 1; /* we need to assume some interval */ + } + } + + gst_object_sync_values (G_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer)); + +wait_for_text_buf: + + GST_OBJECT_LOCK (overlay); + + if (overlay->video_flushing) + goto flushing; + + if (overlay->video_eos) + goto have_eos; + + if (overlay->silent) { + GST_OBJECT_UNLOCK (overlay); + ret = gst_pad_push (overlay->srcpad, buffer); + + /* Update last_stop */ + gst_segment_set_last_stop (&overlay->segment, GST_FORMAT_TIME, clip_start); + + return ret; + } + + /* Text pad not linked, rendering internal text */ + if (!overlay->text_linked) { + if (klass->get_text) { + text = klass->get_text (overlay, buffer); + } else { + text = g_strdup (overlay->default_text); + } + + GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default " + "text: '%s'", GST_STR_NULL (text)); + + GST_OBJECT_UNLOCK (overlay); + + if (text != NULL && *text != '\0') { + /* Render and push */ + gst_base_text_overlay_render_text (overlay, text, -1); + ret = gst_base_text_overlay_push_frame (overlay, buffer); + } else { + /* Invalid or empty string */ + ret = gst_pad_push (overlay->srcpad, buffer); + } + } else { + /* Text pad linked, check if we have a text buffer queued */ + if (overlay->text_buffer) { + gboolean pop_text = FALSE, valid_text_time = TRUE; + GstClockTime text_start = GST_CLOCK_TIME_NONE; + GstClockTime text_end = GST_CLOCK_TIME_NONE; + GstClockTime text_running_time = GST_CLOCK_TIME_NONE; + GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE; + GstClockTime vid_running_time, vid_running_time_end; + + /* 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_IS_VALID (overlay->text_buffer) || + !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) { + GST_WARNING_OBJECT (overlay, + "Got text buffer with invalid timestamp or duration"); + pop_text = TRUE; + valid_text_time = FALSE; + } else { + text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer); + text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer); + } + + vid_running_time = + gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, + start); + vid_running_time_end = + gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, + stop); + + /* If timestamp and duration are valid */ + if (valid_text_time) { + text_running_time = + gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, + text_start); + text_running_time_end = + gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, + text_end); + } + + GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (text_running_time), + GST_TIME_ARGS (text_running_time_end)); + GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (vid_running_time), + GST_TIME_ARGS (vid_running_time_end)); + + /* Text too old or in the future */ + if (valid_text_time && text_running_time_end <= vid_running_time) { + /* text buffer too old, get rid of it and do nothing */ + GST_LOG_OBJECT (overlay, "text buffer too old, popping"); + pop_text = FALSE; + gst_base_text_overlay_pop_text (overlay); + GST_OBJECT_UNLOCK (overlay); + goto wait_for_text_buf; + } else if (valid_text_time && vid_running_time_end <= text_running_time) { + GST_LOG_OBJECT (overlay, "text in future, pushing video buf"); + GST_OBJECT_UNLOCK (overlay); + /* Push the video frame */ + ret = gst_pad_push (overlay->srcpad, buffer); + } else { + gchar *in_text, *otext; + gsize in_size, osize; + + otext = + gst_buffer_map (overlay->text_buffer, &osize, NULL, GST_MAP_READ); + in_text = otext; + in_size = osize; + + /* g_markup_escape_text() absolutely requires valid UTF8 input, it + * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING + * here on purpose, this is something that needs fixing upstream */ + if (!g_utf8_validate (in_text, in_size, NULL)) { + const gchar *end = NULL; + + GST_WARNING_OBJECT (overlay, "received invalid UTF-8"); + in_text = g_strndup (in_text, in_size); + while (!g_utf8_validate (in_text, in_size, &end) && end) + *((gchar *) end) = '*'; + } + + /* Get the string */ + if (overlay->have_pango_markup) { + text = g_strndup (in_text, in_size); + } else { + text = g_markup_escape_text (in_text, in_size); + } + + if (text != NULL && *text != '\0') { + gint text_len = strlen (text); + + while (text_len > 0 && (text[text_len - 1] == '\n' || + text[text_len - 1] == '\r')) { + --text_len; + } + GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text); + gst_base_text_overlay_render_text (overlay, text, text_len); + } else { + GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)"); + gst_base_text_overlay_render_text (overlay, " ", 1); + } + gst_buffer_unmap (overlay->text_buffer, otext, osize); + + if (in_text != otext) + g_free (in_text); + + GST_OBJECT_UNLOCK (overlay); + ret = gst_base_text_overlay_push_frame (overlay, buffer); + + if (valid_text_time && text_running_time_end <= vid_running_time_end) { + GST_LOG_OBJECT (overlay, "text buffer not needed any longer"); + pop_text = TRUE; + } + } + if (pop_text) { + GST_OBJECT_LOCK (overlay); + gst_base_text_overlay_pop_text (overlay); + GST_OBJECT_UNLOCK (overlay); + } + } else { + gboolean wait_for_text_buf = TRUE; + + if (overlay->text_eos) + wait_for_text_buf = FALSE; + + if (!overlay->wait_text) + wait_for_text_buf = FALSE; + + /* Text pad linked, but no text buffer available - what now? */ + if (overlay->text_segment.format == GST_FORMAT_TIME) { + GstClockTime text_start_running_time, text_last_stop_running_time; + GstClockTime vid_running_time; + + vid_running_time = + gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (buffer)); + text_start_running_time = + gst_segment_to_running_time (&overlay->text_segment, + GST_FORMAT_TIME, overlay->text_segment.start); + text_last_stop_running_time = + gst_segment_to_running_time (&overlay->text_segment, + GST_FORMAT_TIME, overlay->text_segment.last_stop); + + if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) && + vid_running_time < text_start_running_time) || + (GST_CLOCK_TIME_IS_VALID (text_last_stop_running_time) && + vid_running_time < text_last_stop_running_time)) { + wait_for_text_buf = FALSE; + } + } + + if (wait_for_text_buf) { + GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one"); + GST_BASE_TEXT_OVERLAY_WAIT (overlay); + GST_DEBUG_OBJECT (overlay, "resuming"); + GST_OBJECT_UNLOCK (overlay); + goto wait_for_text_buf; + } else { + GST_OBJECT_UNLOCK (overlay); + GST_LOG_OBJECT (overlay, "no need to wait for a text buffer"); + ret = gst_pad_push (overlay->srcpad, buffer); + } + } + } + + g_free (text); + + /* Update last_stop */ + gst_segment_set_last_stop (&overlay->segment, GST_FORMAT_TIME, clip_start); + + return ret; + +missing_timestamp: + { + GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding"); + gst_buffer_unref (buffer); + return GST_FLOW_OK; + } + +flushing: + { + GST_OBJECT_UNLOCK (overlay); + GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer"); + gst_buffer_unref (buffer); + return GST_FLOW_WRONG_STATE; + } +have_eos: + { + GST_OBJECT_UNLOCK (overlay); + GST_DEBUG_OBJECT (overlay, "eos, discarding buffer"); + gst_buffer_unref (buffer); + return GST_FLOW_UNEXPECTED; + } +out_of_segment: + { + GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding"); + gst_buffer_unref (buffer); + return GST_FLOW_OK; + } +} + +static GstStateChangeReturn +gst_base_text_overlay_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_OBJECT_LOCK (overlay); + overlay->text_flushing = TRUE; + overlay->video_flushing = TRUE; + /* pop_text will broadcast on the GCond and thus also make the video + * chain exit if it's waiting for a text buffer */ + gst_base_text_overlay_pop_text (overlay); + GST_OBJECT_UNLOCK (overlay); + break; + default: + break; + } + + ret = parent_class->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_OBJECT_LOCK (overlay); + overlay->text_flushing = FALSE; + overlay->video_flushing = FALSE; + overlay->video_eos = FALSE; + overlay->text_eos = FALSE; + gst_segment_init (&overlay->segment, GST_FORMAT_TIME); + gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME); + GST_OBJECT_UNLOCK (overlay); + break; + default: + break; + } + + return ret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gst_controller_init (NULL, NULL); + + if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE, + GST_TYPE_TEXT_OVERLAY) || + !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE, + GST_TYPE_TIME_OVERLAY) || + !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE, + GST_TYPE_CLOCK_OVERLAY) || + !gst_element_register (plugin, "textrender", GST_RANK_NONE, + GST_TYPE_TEXT_RENDER)) { + return FALSE; + } + + /*texttestsrc_plugin_init(module, plugin); */ + + GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements"); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, + "pango", "Pango-based text rendering and overlay", plugin_init, + VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/pango/gstbasetextoverlay.h b/ext/pango/gstbasetextoverlay.h new file mode 100644 index 0000000000..43192292ae --- /dev/null +++ b/ext/pango/gstbasetextoverlay.h @@ -0,0 +1,171 @@ +#ifndef __GST_BASE_TEXT_OVERLAY_H__ +#define __GST_BASE_TEXT_OVERLAY_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_BASE_TEXT_OVERLAY (gst_base_text_overlay_get_type()) +#define GST_BASE_TEXT_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GST_TYPE_BASE_TEXT_OVERLAY, GstBaseTextOverlay)) +#define GST_BASE_TEXT_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\ + GST_TYPE_BASE_TEXT_OVERLAY,GstBaseTextOverlayClass)) +#define GST_BASE_TEXT_OVERLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GST_TYPE_BASE_TEXT_OVERLAY, GstBaseTextOverlayClass)) +#define GST_IS_BASE_TEXT_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\ + GST_TYPE_BASE_TEXT_OVERLAY)) +#define GST_IS_BASE_TEXT_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),\ + GST_TYPE_BASE_TEXT_OVERLAY)) + +typedef struct _GstBaseTextOverlay GstBaseTextOverlay; +typedef struct _GstBaseTextOverlayClass GstBaseTextOverlayClass; + +/** + * GstBaseTextOverlayVAlign: + * @GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE: draw text on the baseline + * @GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM: draw text on the bottom + * @GST_BASE_TEXT_OVERLAY_VALIGN_TOP: draw text on top + * @GST_BASE_TEXT_OVERLAY_VALIGN_POS: draw text according to the #GstBaseTextOverlay:ypos property + * @GST_BASE_TEXT_OVERLAY_VALIGN_CENTER: draw text vertically centered + * + * Vertical alignment of the text. + */ +typedef enum { + GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, + GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, + GST_BASE_TEXT_OVERLAY_VALIGN_TOP, + GST_BASE_TEXT_OVERLAY_VALIGN_POS, + GST_BASE_TEXT_OVERLAY_VALIGN_CENTER +} GstBaseTextOverlayVAlign; + +/** + * GstBaseTextOverlayHAlign: + * @GST_BASE_TEXT_OVERLAY_HALIGN_LEFT: align text left + * @GST_BASE_TEXT_OVERLAY_HALIGN_CENTER: align text center + * @GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT: align text right + * @GST_BASE_TEXT_OVERLAY_HALIGN_POS: position text according to the #GstBaseTextOverlay:xpos property + * + * Horizontal alignment of the text. + */ +/* FIXME 0.11: remove GST_BASE_TEXT_OVERLAY_HALIGN_UNUSED */ +typedef enum { + GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, + GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, + GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, + GST_BASE_TEXT_OVERLAY_HALIGN_UNUSED, + GST_BASE_TEXT_OVERLAY_HALIGN_POS +} GstBaseTextOverlayHAlign; + +/** + * GstBaseTextOverlayWrapMode: + * @GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE: no wrapping + * @GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD: do word wrapping + * @GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR: do char wrapping + * @GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR: do word and char wrapping + * + * Whether to wrap the text and if so how. + */ +typedef enum { + GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE = -1, + GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD = PANGO_WRAP_WORD, + GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR = PANGO_WRAP_CHAR, + GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR = PANGO_WRAP_WORD_CHAR +} GstBaseTextOverlayWrapMode; + +/** + * GstBaseTextOverlayLineAlign: + * @GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT: lines are left-aligned + * @GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER: lines are center-aligned + * @GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT: lines are right-aligned + * + * Alignment of text lines relative to each other + */ +typedef enum { + GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT = PANGO_ALIGN_LEFT, + GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER = PANGO_ALIGN_CENTER, + GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT = PANGO_ALIGN_RIGHT +} GstBaseTextOverlayLineAlign; + +/** + * GstBaseTextOverlay: + * + * Opaque textoverlay object structure + */ +struct _GstBaseTextOverlay { + GstElement element; + + GstPad *video_sinkpad; + GstPad *text_sinkpad; + GstPad *srcpad; + + GstSegment segment; + GstSegment text_segment; + GstBuffer *text_buffer; + gboolean text_linked; + gboolean video_flushing; + gboolean video_eos; + gboolean text_flushing; + gboolean text_eos; + + GCond *cond; /* to signal removal of a queued text + * buffer, arrival of a text buffer, + * a text segment update, or a change + * in status (e.g. shutdown, flushing) */ + + gint width; + gint height; + gint fps_n; + gint fps_d; + GstVideoFormat format; + + GstBaseTextOverlayVAlign valign; + GstBaseTextOverlayHAlign halign; + GstBaseTextOverlayWrapMode wrap_mode; + GstBaseTextOverlayLineAlign line_align; + + gint xpad; + gint ypad; + gint deltax; + gint deltay; + gdouble xpos; + gdouble ypos; + gchar *default_text; + gboolean want_shading; + gboolean silent; + gboolean wait_text; + guint color; + + PangoLayout *layout; + gdouble shadow_offset; + gdouble outline_offset; + guchar *text_image; + gint image_width; + gint image_height; + gint baseline_y; + + gboolean auto_adjust_size; + gboolean need_render; + + gint shading_value; /* for timeoverlay subclass */ + + gboolean have_pango_markup; + gboolean use_vertical_render; +}; + +struct _GstBaseTextOverlayClass { + GstElementClass parent_class; + + PangoContext *pango_context; + GMutex *pango_lock; + + gchar * (*get_text) (GstBaseTextOverlay *overlay, GstBuffer *video_frame); +}; + +GType gst_base_text_overlay_get_type(void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GST_BASE_TEXT_OVERLAY_H */ diff --git a/ext/pango/gstclockoverlay.c b/ext/pango/gstclockoverlay.c index 026894d529..7321e4e6f1 100644 --- a/ext/pango/gstclockoverlay.c +++ b/ext/pango/gstclockoverlay.c @@ -20,11 +20,11 @@ /** * SECTION:element-clockoverlay - * @see_also: #GstTextOverlay, #GstTimeOverlay + * @see_also: #GstBaseTextOverlay, #GstTimeOverlay * * This element overlays the current clock time on top of a video * stream. You can position the text and configure the font details - * using the properties of the #GstTextOverlay class. By default, the + * using the properties of the #GstBaseTextOverlay class. By default, the * time is displayed in the top left corner of the picture, with some * padding to the left and to the top. * @@ -60,8 +60,8 @@ enum PROP_LAST }; -GST_BOILERPLATE (GstClockOverlay, gst_clock_overlay, GstTextOverlay, - GST_TYPE_TEXT_OVERLAY); +GST_BOILERPLATE (GstClockOverlay, gst_clock_overlay, GstBaseTextOverlay, + GST_TYPE_BASE_TEXT_OVERLAY); static void gst_clock_overlay_base_init (gpointer g_class) @@ -114,7 +114,8 @@ gst_clock_overlay_render_time (GstClockOverlay * overlay) /* Called with lock held */ static gchar * -gst_clock_overlay_get_text (GstTextOverlay * overlay, GstBuffer * video_frame) +gst_clock_overlay_get_text (GstBaseTextOverlay * overlay, + GstBuffer * video_frame) { gchar *time_str, *txt, *ret; GstClockOverlay *clock_overlay = GST_CLOCK_OVERLAY (overlay); @@ -145,10 +146,10 @@ static void gst_clock_overlay_class_init (GstClockOverlayClass * klass) { GObjectClass *gobject_class; - GstTextOverlayClass *gsttextoverlay_class; + GstBaseTextOverlayClass *gsttextoverlay_class; gobject_class = (GObjectClass *) klass; - gsttextoverlay_class = (GstTextOverlayClass *) klass; + gsttextoverlay_class = (GstBaseTextOverlayClass *) klass; gobject_class->finalize = gst_clock_overlay_finalize; gobject_class->set_property = gst_clock_overlay_set_property; @@ -180,12 +181,12 @@ static void gst_clock_overlay_init (GstClockOverlay * overlay, GstClockOverlayClass * klass) { PangoFontDescription *font_description; - GstTextOverlay *textoverlay; + GstBaseTextOverlay *textoverlay; PangoContext *context; - textoverlay = GST_TEXT_OVERLAY (overlay); + textoverlay = GST_BASE_TEXT_OVERLAY (overlay); - context = GST_TEXT_OVERLAY_CLASS (klass)->pango_context; + context = GST_BASE_TEXT_OVERLAY_CLASS (klass)->pango_context; pango_context_set_language (context, pango_language_from_string ("en_US")); pango_context_set_base_dir (context, PANGO_DIRECTION_LTR); @@ -200,8 +201,8 @@ gst_clock_overlay_init (GstClockOverlay * overlay, GstClockOverlayClass * klass) pango_context_set_font_description (context, font_description); pango_font_description_free (font_description); - textoverlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP; - textoverlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT; + textoverlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP; + textoverlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_LEFT; overlay->format = g_strdup (DEFAULT_PROP_TIMEFORMAT); } diff --git a/ext/pango/gstclockoverlay.h b/ext/pango/gstclockoverlay.h index 15a82ed795..09ca2d3939 100644 --- a/ext/pango/gstclockoverlay.h +++ b/ext/pango/gstclockoverlay.h @@ -22,7 +22,7 @@ #ifndef __GST_CLOCK_OVERLAY_H__ #define __GST_CLOCK_OVERLAY_H__ -#include "gsttextoverlay.h" +#include "gstbasetextoverlay.h" G_BEGIN_DECLS @@ -46,13 +46,13 @@ typedef struct _GstClockOverlayClass GstClockOverlayClass; * Opaque clockoverlay data structure. */ struct _GstClockOverlay { - GstTextOverlay textoverlay; + GstBaseTextOverlay textoverlay; gchar *format; /* as in strftime () */ gchar *text; }; struct _GstClockOverlayClass { - GstTextOverlayClass parent_class; + GstBaseTextOverlayClass parent_class; }; GType gst_clock_overlay_get_type (void); diff --git a/ext/pango/gsttextoverlay.c b/ext/pango/gsttextoverlay.c index 683a4783d4..dd8114e8b1 100644 --- a/ext/pango/gsttextoverlay.c +++ b/ext/pango/gsttextoverlay.c @@ -5,6 +5,7 @@ * Copyright (C) <2006> Zeeshan Ali * Copyright (C) <2006-2008> Tim-Philipp Müller * Copyright (C) <2009> Young-Ho Cha + * Copyright (C) <2011> Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -24,7 +25,7 @@ /** * SECTION:element-textoverlay - * @see_also: #GstTextRender, #GstClockOverlay, #GstTimeOverlay, #GstSubParse + * @see_also: #GstTextRender, #GstTextOverlay, #GstTimeOverlay, #GstSubParse * * This plugin renders text on top of a video stream. This can be either * static text or text from buffers received on the text sink pad, e.g. @@ -69,154 +70,11 @@ * */ -/* FIXME: alloc segment as part of instance struct */ - #ifdef HAVE_CONFIG_H -#include +#include "config.h" #endif -#include - -#include "gsttextoverlay.h" -#include "gsttimeoverlay.h" -#include "gstclockoverlay.h" -#include "gsttextrender.h" -#include - -/* FIXME: - * - use proper strides and offset for I420 - * - if text is wider than the video picture, it does not get - * clipped properly during blitting (if wrapping is disabled) - * - make 'shading_value' a property (or enum: light/normal/dark/verydark)? - */ - -GST_DEBUG_CATEGORY (pango_debug); -#define GST_CAT_DEFAULT pango_debug - -#define DEFAULT_PROP_TEXT "" -#define DEFAULT_PROP_SHADING FALSE -#define DEFAULT_PROP_VALIGNMENT GST_TEXT_OVERLAY_VALIGN_BASELINE -#define DEFAULT_PROP_HALIGNMENT GST_TEXT_OVERLAY_HALIGN_CENTER -#define DEFAULT_PROP_VALIGN "baseline" -#define DEFAULT_PROP_HALIGN "center" -#define DEFAULT_PROP_XPAD 25 -#define DEFAULT_PROP_YPAD 25 -#define DEFAULT_PROP_DELTAX 0 -#define DEFAULT_PROP_DELTAY 0 -#define DEFAULT_PROP_XPOS 0.5 -#define DEFAULT_PROP_YPOS 0.5 -#define DEFAULT_PROP_WRAP_MODE GST_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR -#define DEFAULT_PROP_FONT_DESC "" -#define DEFAULT_PROP_SILENT FALSE -#define DEFAULT_PROP_LINE_ALIGNMENT GST_TEXT_OVERLAY_LINE_ALIGN_CENTER -#define DEFAULT_PROP_WAIT_TEXT TRUE -#define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE -#define DEFAULT_PROP_VERTICAL_RENDER FALSE -#define DEFAULT_PROP_COLOR 0xffffffff - -/* make a property of me */ -#define DEFAULT_SHADING_VALUE -80 - -#define MINIMUM_OUTLINE_OFFSET 1.0 -#define DEFAULT_SCALE_BASIS 640 - -#define COMP_Y(ret, r, g, b) \ -{ \ - ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \ - ret = CLAMP (ret, 0, 255); \ -} - -#define COMP_U(ret, r, g, b) \ -{ \ - ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \ - ret = CLAMP (ret, 0, 255); \ -} - -#define COMP_V(ret, r, g, b) \ -{ \ - ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \ - ret = CLAMP (ret, 0, 255); \ -} - -#define BLEND(ret, alpha, v0, v1) \ -{ \ - ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \ -} - -#define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew) \ -{ \ - gint _tmp; \ - _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \ - ret = CLAMP (_tmp, 0, 255); \ -} - -#if G_BYTE_ORDER == G_LITTLE_ENDIAN -# define CAIRO_ARGB_A 3 -# define CAIRO_ARGB_R 2 -# define CAIRO_ARGB_G 1 -# define CAIRO_ARGB_B 0 -#else -# define CAIRO_ARGB_A 0 -# define CAIRO_ARGB_R 1 -# define CAIRO_ARGB_G 2 -# define CAIRO_ARGB_B 3 -#endif - -enum -{ - PROP_0, - PROP_TEXT, - PROP_SHADING, - PROP_VALIGN, /* deprecated */ - PROP_HALIGN, /* deprecated */ - PROP_HALIGNMENT, - PROP_VALIGNMENT, - PROP_XPAD, - PROP_YPAD, - PROP_DELTAX, - PROP_DELTAY, - PROP_XPOS, - PROP_YPOS, - PROP_WRAP_MODE, - PROP_FONT_DESC, - PROP_SILENT, - PROP_LINE_ALIGNMENT, - PROP_WAIT_TEXT, - PROP_AUTO_ADJUST_SIZE, - PROP_VERTICAL_RENDER, - PROP_COLOR, - PROP_LAST -}; - -static GstStaticPadTemplate src_template_factory = - GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx ";" - GST_VIDEO_CAPS_RGBx ";" - GST_VIDEO_CAPS_xRGB ";" - GST_VIDEO_CAPS_xBGR ";" - GST_VIDEO_CAPS_RGBA ";" - GST_VIDEO_CAPS_BGRA ";" - GST_VIDEO_CAPS_ARGB ";" - GST_VIDEO_CAPS_ABGR ";" - GST_VIDEO_CAPS_YUV ("{AYUV, I420, UYVY, NV12, NV21}")) - ); - -static GstStaticPadTemplate video_sink_template_factory = - GST_STATIC_PAD_TEMPLATE ("video_sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS (GST_VIDEO_CAPS_BGRx ";" - GST_VIDEO_CAPS_RGBx ";" - GST_VIDEO_CAPS_xRGB ";" - GST_VIDEO_CAPS_xBGR ";" - GST_VIDEO_CAPS_RGBA ";" - GST_VIDEO_CAPS_BGRA ";" - GST_VIDEO_CAPS_ARGB ";" - GST_VIDEO_CAPS_ABGR ";" - GST_VIDEO_CAPS_YUV ("{AYUV, I420, UYVY, NV12, NV21}")) - ); +#include static GstStaticPadTemplate text_sink_template_factory = GST_STATIC_PAD_TEMPLATE ("text_sink", @@ -225,145 +83,15 @@ static GstStaticPadTemplate text_sink_template_factory = GST_STATIC_CAPS ("text/x-pango-markup; text/plain") ); -#define GST_TYPE_TEXT_OVERLAY_VALIGN (gst_text_overlay_valign_get_type()) -static GType -gst_text_overlay_valign_get_type (void) -{ - static GType text_overlay_valign_type = 0; - static const GEnumValue text_overlay_valign[] = { - {GST_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"}, - {GST_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"}, - {GST_TEXT_OVERLAY_VALIGN_TOP, "top", "top"}, - {GST_TEXT_OVERLAY_VALIGN_POS, "position", "position"}, - {GST_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"}, - {0, NULL, NULL}, - }; - - if (!text_overlay_valign_type) { - text_overlay_valign_type = - g_enum_register_static ("GstTextOverlayVAlign", text_overlay_valign); - } - return text_overlay_valign_type; -} - -#define GST_TYPE_TEXT_OVERLAY_HALIGN (gst_text_overlay_halign_get_type()) -static GType -gst_text_overlay_halign_get_type (void) -{ - static GType text_overlay_halign_type = 0; - static const GEnumValue text_overlay_halign[] = { - {GST_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"}, - {GST_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"}, - {GST_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"}, - {GST_TEXT_OVERLAY_HALIGN_POS, "position", "position"}, - {0, NULL, NULL}, - }; - - if (!text_overlay_halign_type) { - text_overlay_halign_type = - g_enum_register_static ("GstTextOverlayHAlign", text_overlay_halign); - } - return text_overlay_halign_type; -} - - -#define GST_TYPE_TEXT_OVERLAY_WRAP_MODE (gst_text_overlay_wrap_mode_get_type()) -static GType -gst_text_overlay_wrap_mode_get_type (void) -{ - static GType text_overlay_wrap_mode_type = 0; - static const GEnumValue text_overlay_wrap_mode[] = { - {GST_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"}, - {GST_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"}, - {GST_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"}, - {GST_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"}, - {0, NULL, NULL}, - }; - - if (!text_overlay_wrap_mode_type) { - text_overlay_wrap_mode_type = - g_enum_register_static ("GstTextOverlayWrapMode", - text_overlay_wrap_mode); - } - return text_overlay_wrap_mode_type; -} - -#define GST_TYPE_TEXT_OVERLAY_LINE_ALIGN (gst_text_overlay_line_align_get_type()) -static GType -gst_text_overlay_line_align_get_type (void) -{ - static GType text_overlay_line_align_type = 0; - static const GEnumValue text_overlay_line_align[] = { - {GST_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"}, - {GST_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"}, - {GST_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"}, - {0, NULL, NULL} - }; - - if (!text_overlay_line_align_type) { - text_overlay_line_align_type = - g_enum_register_static ("GstTextOverlayLineAlign", - text_overlay_line_align); - } - return text_overlay_line_align_type; -} - -#define GST_TEXT_OVERLAY_GET_COND(ov) (((GstTextOverlay *)ov)->cond) -#define GST_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_TEXT_OVERLAY_GET_COND (ov), GST_OBJECT_GET_LOCK (ov))) -#define GST_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_TEXT_OVERLAY_GET_COND (ov))) -#define GST_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_TEXT_OVERLAY_GET_COND (ov))) - -static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element, - GstStateChange transition); - -static GstCaps *gst_text_overlay_getcaps (GstPad * pad); -static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps); -static gboolean gst_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps); -static gboolean gst_text_overlay_src_event (GstPad * pad, GstEvent * event); -static gboolean gst_text_overlay_src_query (GstPad * pad, GstQuery * query); - -static gboolean gst_text_overlay_video_event (GstPad * pad, GstEvent * event); -static GstFlowReturn gst_text_overlay_video_chain (GstPad * pad, - GstBuffer * buffer); -static GstFlowReturn gst_text_overlay_video_bufferalloc (GstPad * pad, - guint64 offset, guint size, GstCaps * caps, GstBuffer ** buffer); - -static gboolean gst_text_overlay_text_event (GstPad * pad, GstEvent * event); -static GstFlowReturn gst_text_overlay_text_chain (GstPad * pad, - GstBuffer * buffer); -static GstPadLinkReturn gst_text_overlay_text_pad_link (GstPad * pad, - GstPad * peer); -static void gst_text_overlay_text_pad_unlink (GstPad * pad); -static void gst_text_overlay_pop_text (GstTextOverlay * overlay); -static void gst_text_overlay_update_render_mode (GstTextOverlay * overlay); - -static void gst_text_overlay_finalize (GObject * object); -static void gst_text_overlay_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec); -static void gst_text_overlay_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec); -static void gst_text_overlay_adjust_values_with_fontdesc (GstTextOverlay * - overlay, PangoFontDescription * desc); - -GST_BOILERPLATE (GstTextOverlay, gst_text_overlay, GstElement, - GST_TYPE_ELEMENT); +G_DEFINE_TYPE (GstTextOverlay, gst_text_overlay, GST_TYPE_BASE_TEXT_OVERLAY); static void -gst_text_overlay_base_init (gpointer g_class) +gst_text_overlay_class_init (GstTextOverlayClass * klass) { - GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + GstElementClass *element_class = (GstElementClass *) klass; gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&src_template_factory)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&video_sink_template_factory)); - - /* ugh */ - if (!GST_IS_TIME_OVERLAY_CLASS (g_class) && - !GST_IS_CLOCK_OVERLAY_CLASS (g_class)) { - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&text_sink_template_factory)); - } + gst_static_pad_template_get (&text_sink_template_factory)); gst_element_class_set_details_simple (element_class, "Text overlay", "Filter/Editor/Video", @@ -371,2358 +99,7 @@ gst_text_overlay_base_init (gpointer g_class) "David Schleef , " "Zeeshan Ali "); } -static gchar * -gst_text_overlay_get_text (GstTextOverlay * overlay, GstBuffer * video_frame) -{ - return g_strdup (overlay->default_text); -} - static void -gst_text_overlay_class_init (GstTextOverlayClass * klass) +gst_text_overlay_init (GstTextOverlay * overlay) { - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - PangoFontMap *fontmap; - - gobject_class = (GObjectClass *) klass; - gstelement_class = (GstElementClass *) klass; - - gobject_class->finalize = gst_text_overlay_finalize; - gobject_class->set_property = gst_text_overlay_set_property; - gobject_class->get_property = gst_text_overlay_get_property; - - gstelement_class->change_state = - GST_DEBUG_FUNCPTR (gst_text_overlay_change_state); - - klass->pango_lock = g_mutex_new (); - - klass->get_text = gst_text_overlay_get_text; - fontmap = pango_cairo_font_map_get_default (); - klass->pango_context = - pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (fontmap)); - - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT, - g_param_spec_string ("text", "text", - "Text to be display.", DEFAULT_PROP_TEXT, - G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING, - g_param_spec_boolean ("shaded-background", "shaded background", - "Whether to shade the background under the text area", - DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT, - g_param_spec_enum ("valignment", "vertical alignment", - "Vertical alignment of the text", GST_TYPE_TEXT_OVERLAY_VALIGN, - DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT, - g_param_spec_enum ("halignment", "horizontal alignment", - "Horizontal alignment of the text", GST_TYPE_TEXT_OVERLAY_HALIGN, - DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGN, - g_param_spec_string ("valign", "vertical alignment", - "Vertical alignment of the text (deprecated; use valignment)", - DEFAULT_PROP_VALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGN, - g_param_spec_string ("halign", "horizontal alignment", - "Horizontal alignment of the text (deprecated; use halignment)", - DEFAULT_PROP_HALIGN, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD, - g_param_spec_int ("xpad", "horizontal paddding", - "Horizontal paddding when using left/right alignment", 0, G_MAXINT, - DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD, - g_param_spec_int ("ypad", "vertical padding", - "Vertical padding when using top/bottom alignment", 0, G_MAXINT, - DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_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, DEFAULT_PROP_DELTAX, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY, - g_param_spec_int ("deltay", "Y position modifier", - "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT, - DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - /** - * GstTextOverlay:xpos - * - * Horizontal position of the rendered text when using positioned alignment. - * - * Since: 0.10.31 - **/ - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS, - g_param_spec_double ("xpos", "horizontal position", - "Horizontal position when using position alignment", 0, 1.0, - DEFAULT_PROP_XPOS, - G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); - /** - * GstTextOverlay:ypos - * - * Vertical position of the rendered text when using positioned alignment. - * - * Since: 0.10.31 - **/ - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS, - g_param_spec_double ("ypos", "vertical position", - "Vertical position when using position alignment", 0, 1.0, - DEFAULT_PROP_YPOS, - G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE, - g_param_spec_enum ("wrap-mode", "wrap mode", - "Whether to wrap the text and if so how.", - GST_TYPE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC, - g_param_spec_string ("font-desc", "font description", - "Pango font description of font to be used for rendering. " - "See documentation of pango_font_description_from_string " - "for syntax.", DEFAULT_PROP_FONT_DESC, - G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); - /** - * GstTextOverlay:color - * - * Color of the rendered text. - * - * Since: 0.10.31 - **/ - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR, - g_param_spec_uint ("color", "Color", - "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32, - DEFAULT_PROP_COLOR, - G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); - - /** - * GstTextOverlay:line-alignment - * - * Alignment of text lines relative to each other (for multi-line text) - * - * Since: 0.10.15 - **/ - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT, - g_param_spec_enum ("line-alignment", "line alignment", - "Alignment of text lines relative to each other.", - GST_TYPE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - /** - * GstTextOverlay:silent - * - * If set, no text is rendered. Useful to switch off text rendering - * temporarily without removing the textoverlay element from the pipeline. - * - * Since: 0.10.15 - **/ - /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */ - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT, - g_param_spec_boolean ("silent", "silent", - "Whether to render the text string", - DEFAULT_PROP_SILENT, - G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); - /** - * GstTextOverlay:wait-text - * - * If set, the video will block until a subtitle is received on the text pad. - * If video and subtitles are sent in sync, like from the same demuxer, this - * property should be set. - * - * Since: 0.10.20 - **/ - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT, - g_param_spec_boolean ("wait-text", "Wait Text", - "Whether to wait for subtitles", - DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (G_OBJECT_CLASS (klass), - PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize", - "Automatically adjust font size to screen-size.", - DEFAULT_PROP_AUTO_ADJUST_SIZE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER, - g_param_spec_boolean ("vertical-render", "vertical render", - "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } - -static void -gst_text_overlay_finalize (GObject * object) -{ - GstTextOverlay *overlay = GST_TEXT_OVERLAY (object); - - g_free (overlay->default_text); - - if (overlay->text_image) { - g_free (overlay->text_image); - overlay->text_image = NULL; - } - - if (overlay->layout) { - g_object_unref (overlay->layout); - overlay->layout = NULL; - } - - if (overlay->text_buffer) { - gst_buffer_unref (overlay->text_buffer); - overlay->text_buffer = NULL; - } - - if (overlay->cond) { - g_cond_free (overlay->cond); - overlay->cond = NULL; - } - - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -static void -gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass) -{ - GstPadTemplate *template; - PangoFontDescription *desc; - - /* video sink */ - template = gst_static_pad_template_get (&video_sink_template_factory); - overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink"); - gst_object_unref (template); - 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_pad_set_event_function (overlay->video_sinkpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_video_event)); - gst_pad_set_chain_function (overlay->video_sinkpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_video_chain)); - gst_pad_set_bufferalloc_function (overlay->video_sinkpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_video_bufferalloc)); - gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad); - - if (!GST_IS_TIME_OVERLAY_CLASS (klass) && !GST_IS_CLOCK_OVERLAY_CLASS (klass)) { - /* text sink */ - template = gst_static_pad_template_get (&text_sink_template_factory); - overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink"); - gst_object_unref (template); - gst_pad_set_setcaps_function (overlay->text_sinkpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps_txt)); - gst_pad_set_event_function (overlay->text_sinkpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_text_event)); - gst_pad_set_chain_function (overlay->text_sinkpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_text_chain)); - gst_pad_set_link_function (overlay->text_sinkpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_link)); - gst_pad_set_unlink_function (overlay->text_sinkpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlink)); - gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad); - } - - /* (video) source */ - template = gst_static_pad_template_get (&src_template_factory); - overlay->srcpad = gst_pad_new_from_template (template, "src"); - gst_object_unref (template); - gst_pad_set_getcaps_function (overlay->srcpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps)); - gst_pad_set_event_function (overlay->srcpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_src_event)); - gst_pad_set_query_function (overlay->srcpad, - GST_DEBUG_FUNCPTR (gst_text_overlay_src_query)); - gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad); - - overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT; - overlay->layout = - pango_layout_new (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_context); - desc = - pango_context_get_font_description (GST_TEXT_OVERLAY_GET_CLASS - (overlay)->pango_context); - gst_text_overlay_adjust_values_with_fontdesc (overlay, desc); - - overlay->color = DEFAULT_PROP_COLOR; - overlay->halign = DEFAULT_PROP_HALIGNMENT; - overlay->valign = DEFAULT_PROP_VALIGNMENT; - overlay->xpad = DEFAULT_PROP_XPAD; - overlay->ypad = DEFAULT_PROP_YPAD; - overlay->deltax = DEFAULT_PROP_DELTAX; - overlay->deltay = DEFAULT_PROP_DELTAY; - overlay->xpos = DEFAULT_PROP_XPOS; - overlay->ypos = DEFAULT_PROP_YPOS; - - overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE; - - overlay->want_shading = DEFAULT_PROP_SHADING; - overlay->shading_value = DEFAULT_SHADING_VALUE; - overlay->silent = DEFAULT_PROP_SILENT; - overlay->wait_text = DEFAULT_PROP_WAIT_TEXT; - overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE; - - overlay->default_text = g_strdup (DEFAULT_PROP_TEXT); - overlay->need_render = TRUE; - overlay->text_image = NULL; - overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER; - gst_text_overlay_update_render_mode (overlay); - - overlay->fps_n = 0; - overlay->fps_d = 1; - - overlay->text_buffer = NULL; - overlay->text_linked = FALSE; - overlay->cond = g_cond_new (); - gst_segment_init (&overlay->segment, GST_FORMAT_TIME); -} - -static void -gst_text_overlay_update_wrap_mode (GstTextOverlay * overlay) -{ - if (overlay->wrap_mode == GST_TEXT_OVERLAY_WRAP_MODE_NONE) { - GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE"); - pango_layout_set_width (overlay->layout, -1); - } else { - int width; - - if (overlay->auto_adjust_size) { - width = DEFAULT_SCALE_BASIS * PANGO_SCALE; - if (overlay->use_vertical_render) { - width = width * (overlay->height - overlay->ypad * 2) / overlay->width; - } - } else { - width = - (overlay->use_vertical_render ? overlay->height : overlay->width) * - PANGO_SCALE; - } - - GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width); - GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode); - pango_layout_set_width (overlay->layout, width); - pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode); - } -} - -static void -gst_text_overlay_update_render_mode (GstTextOverlay * overlay) -{ - PangoMatrix matrix = PANGO_MATRIX_INIT; - PangoContext *context = pango_layout_get_context (overlay->layout); - - if (overlay->use_vertical_render) { - pango_matrix_rotate (&matrix, -90); - pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO); - pango_context_set_matrix (context, &matrix); - pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT); - } else { - pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH); - pango_context_set_matrix (context, &matrix); - pango_layout_set_alignment (overlay->layout, overlay->line_align); - } -} - -static gboolean -gst_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps) -{ - GstTextOverlay *overlay; - GstStructure *structure; - - overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - - structure = gst_caps_get_structure (caps, 0); - overlay->have_pango_markup = - gst_structure_has_name (structure, "text/x-pango-markup"); - - gst_object_unref (overlay); - - return TRUE; -} - -/* 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; - const GValue *fps; - - if (!GST_PAD_IS_SINK (pad)) - return TRUE; - - g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); - - overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - - overlay->width = 0; - overlay->height = 0; - structure = gst_caps_get_structure (caps, 0); - fps = gst_structure_get_value (structure, "framerate"); - - if (fps - && gst_video_format_parse_caps (caps, &overlay->format, &overlay->width, - &overlay->height)) { - ret = gst_pad_set_caps (overlay->srcpad, caps); - } - - overlay->fps_n = gst_value_get_fraction_numerator (fps); - overlay->fps_d = gst_value_get_fraction_denominator (fps); - - if (ret) { - GST_OBJECT_LOCK (overlay); - gst_text_overlay_update_wrap_mode (overlay); - GST_OBJECT_UNLOCK (overlay); - } - - gst_object_unref (overlay); - - return ret; -} - -static void -gst_text_overlay_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec) -{ - GstTextOverlay *overlay = GST_TEXT_OVERLAY (object); - - GST_OBJECT_LOCK (overlay); - switch (prop_id) { - case PROP_TEXT: - g_free (overlay->default_text); - overlay->default_text = g_value_dup_string (value); - overlay->need_render = TRUE; - break; - case PROP_SHADING: - overlay->want_shading = g_value_get_boolean (value); - break; - case PROP_XPAD: - overlay->xpad = g_value_get_int (value); - break; - case PROP_YPAD: - overlay->ypad = g_value_get_int (value); - break; - case PROP_DELTAX: - overlay->deltax = g_value_get_int (value); - break; - case PROP_DELTAY: - overlay->deltay = g_value_get_int (value); - break; - case PROP_XPOS: - overlay->xpos = g_value_get_double (value); - break; - case PROP_YPOS: - overlay->ypos = g_value_get_double (value); - break; - case PROP_HALIGN:{ - const gchar *s = g_value_get_string (value); - - if (s && g_ascii_strcasecmp (s, "left") == 0) - overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT; - else if (s && g_ascii_strcasecmp (s, "center") == 0) - overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER; - else if (s && g_ascii_strcasecmp (s, "right") == 0) - overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT; - else - g_warning ("Invalid value '%s' for textoverlay property 'halign'", - GST_STR_NULL (s)); - break; - } - case PROP_VALIGN:{ - const gchar *s = g_value_get_string (value); - - if (s && g_ascii_strcasecmp (s, "baseline") == 0) - overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE; - else if (s && g_ascii_strcasecmp (s, "bottom") == 0) - overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM; - else if (s && g_ascii_strcasecmp (s, "top") == 0) - overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP; - else - g_warning ("Invalid value '%s' for textoverlay property 'valign'", - GST_STR_NULL (s)); - break; - } - case PROP_VALIGNMENT: - overlay->valign = g_value_get_enum (value); - break; - case PROP_HALIGNMENT: - overlay->halign = g_value_get_enum (value); - break; - case PROP_WRAP_MODE: - overlay->wrap_mode = g_value_get_enum (value); - gst_text_overlay_update_wrap_mode (overlay); - break; - case PROP_FONT_DESC: - { - PangoFontDescription *desc; - const gchar *fontdesc_str; - - fontdesc_str = g_value_get_string (value); - desc = pango_font_description_from_string (fontdesc_str); - if (desc) { - GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str); - pango_layout_set_font_description (overlay->layout, desc); - gst_text_overlay_adjust_values_with_fontdesc (overlay, desc); - pango_font_description_free (desc); - } else { - GST_WARNING_OBJECT (overlay, "font description parse failed: %s", - fontdesc_str); - } - break; - } - case PROP_COLOR: - overlay->color = g_value_get_uint (value); - break; - case PROP_SILENT: - overlay->silent = g_value_get_boolean (value); - break; - case PROP_LINE_ALIGNMENT: - overlay->line_align = g_value_get_enum (value); - pango_layout_set_alignment (overlay->layout, - (PangoAlignment) overlay->line_align); - break; - case PROP_WAIT_TEXT: - overlay->wait_text = g_value_get_boolean (value); - break; - case PROP_AUTO_ADJUST_SIZE: - overlay->auto_adjust_size = g_value_get_boolean (value); - overlay->need_render = TRUE; - break; - case PROP_VERTICAL_RENDER: - overlay->use_vertical_render = g_value_get_boolean (value); - gst_text_overlay_update_render_mode (overlay); - overlay->need_render = TRUE; - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } - - overlay->need_render = TRUE; - GST_OBJECT_UNLOCK (overlay); -} - -static void -gst_text_overlay_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec) -{ - GstTextOverlay *overlay = GST_TEXT_OVERLAY (object); - - GST_OBJECT_LOCK (overlay); - switch (prop_id) { - case PROP_TEXT: - g_value_set_string (value, overlay->default_text); - break; - case PROP_SHADING: - g_value_set_boolean (value, overlay->want_shading); - break; - case PROP_XPAD: - g_value_set_int (value, overlay->xpad); - break; - case PROP_YPAD: - g_value_set_int (value, overlay->ypad); - break; - case PROP_DELTAX: - g_value_set_int (value, overlay->deltax); - break; - case PROP_DELTAY: - g_value_set_int (value, overlay->deltay); - break; - case PROP_XPOS: - g_value_set_double (value, overlay->xpos); - break; - case PROP_YPOS: - g_value_set_double (value, overlay->ypos); - break; - case PROP_VALIGNMENT: - g_value_set_enum (value, overlay->valign); - break; - case PROP_HALIGNMENT: - g_value_set_enum (value, overlay->halign); - break; - case PROP_WRAP_MODE: - g_value_set_enum (value, overlay->wrap_mode); - break; - case PROP_SILENT: - g_value_set_boolean (value, overlay->silent); - break; - case PROP_LINE_ALIGNMENT: - g_value_set_enum (value, overlay->line_align); - break; - case PROP_WAIT_TEXT: - g_value_set_boolean (value, overlay->wait_text); - break; - case PROP_AUTO_ADJUST_SIZE: - g_value_set_boolean (value, overlay->auto_adjust_size); - break; - case PROP_VERTICAL_RENDER: - g_value_set_boolean (value, overlay->use_vertical_render); - break; - case PROP_COLOR: - g_value_set_uint (value, overlay->color); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } - - overlay->need_render = TRUE; - GST_OBJECT_UNLOCK (overlay); -} - -static gboolean -gst_text_overlay_src_query (GstPad * pad, GstQuery * query) -{ - gboolean ret = FALSE; - GstTextOverlay *overlay = NULL; - - overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - - ret = gst_pad_peer_query (overlay->video_sinkpad, query); - - gst_object_unref (overlay); - - return ret; -} - -static gboolean -gst_text_overlay_src_event (GstPad * pad, GstEvent * event) -{ - gboolean ret = FALSE; - GstTextOverlay *overlay = NULL; - - overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_SEEK:{ - GstSeekFlags flags; - - /* We don't handle seek if we have not text pad */ - if (!overlay->text_linked) { - GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream"); - ret = gst_pad_push_event (overlay->video_sinkpad, event); - goto beach; - } - - GST_DEBUG_OBJECT (overlay, "seek received, driving from here"); - - gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL); - - /* Flush downstream, only for flushing seek */ - if (flags & GST_SEEK_FLAG_FLUSH) - gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ()); - - /* Mark ourself as flushing, unblock chains */ - GST_OBJECT_LOCK (overlay); - overlay->video_flushing = TRUE; - overlay->text_flushing = TRUE; - gst_text_overlay_pop_text (overlay); - GST_OBJECT_UNLOCK (overlay); - - /* Seek on each sink pad */ - gst_event_ref (event); - ret = gst_pad_push_event (overlay->video_sinkpad, event); - if (ret) { - ret = gst_pad_push_event (overlay->text_sinkpad, event); - } else { - gst_event_unref (event); - } - break; - } - default: - if (overlay->text_linked) { - gst_event_ref (event); - ret = gst_pad_push_event (overlay->video_sinkpad, event); - gst_pad_push_event (overlay->text_sinkpad, event); - } else { - ret = gst_pad_push_event (overlay->video_sinkpad, event); - } - break; - } - -beach: - gst_object_unref (overlay); - - return ret; -} - -static GstCaps * -gst_text_overlay_getcaps (GstPad * pad) -{ - GstTextOverlay *overlay; - GstPad *otherpad; - GstCaps *caps; - - overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - - 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; -} - -static void -gst_text_overlay_adjust_values_with_fontdesc (GstTextOverlay * overlay, - PangoFontDescription * desc) -{ - gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE; - overlay->shadow_offset = (double) (font_size) / 13.0; - overlay->outline_offset = (double) (font_size) / 15.0; - if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET) - overlay->outline_offset = MINIMUM_OUTLINE_OFFSET; -} - -#define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \ - b = (a > 0) ? MIN ((b * 255 + a / 2) / a, 255) : 0; \ - g = (a > 0) ? MIN ((g * 255 + a / 2) / a, 255) : 0; \ - r = (a > 0) ? MIN ((r * 255 + a / 2) / a, 255) : 0; \ -} G_STMT_END - -static inline void -gst_text_overlay_blit_1 (GstTextOverlay * overlay, guchar * dest, gint xpos, - gint ypos, guchar * text_image, guint dest_stride) -{ - gint i, j = 0; - gint x, y; - guchar r, g, b, a; - guchar *pimage; - guchar *py; - gint width = overlay->image_width; - gint height = overlay->image_height; - - if (xpos < 0) { - xpos = 0; - } - - if (xpos + width > overlay->width) { - width = overlay->width - xpos; - } - - if (ypos + height > overlay->height) { - height = overlay->height - ypos; - } - - dest += (ypos / 1) * dest_stride; - - for (i = 0; i < height; i++) { - pimage = text_image + 4 * (i * overlay->image_width); - py = dest + i * dest_stride + xpos; - for (j = 0; j < width; j++) { - b = pimage[CAIRO_ARGB_B]; - g = pimage[CAIRO_ARGB_G]; - r = pimage[CAIRO_ARGB_R]; - a = pimage[CAIRO_ARGB_A]; - CAIRO_UNPREMULTIPLY (a, r, g, b); - - pimage += 4; - if (a == 0) { - py++; - continue; - } - COMP_Y (y, r, g, b); - x = *py; - BLEND (*py++, a, y, x); - } - } -} - -static inline void -gst_text_overlay_blit_sub2x2cbcr (GstTextOverlay * overlay, - guchar * destcb, guchar * destcr, gint xpos, gint ypos, guchar * text_image, - guint destcb_stride, guint destcr_stride, guint pix_stride) -{ - gint i, j; - gint x, cb, cr; - gushort r, g, b, a; - gushort r1, g1, b1, a1; - guchar *pimage1, *pimage2; - guchar *pcb, *pcr; - gint width = overlay->image_width - 2; - gint height = overlay->image_height - 2; - - xpos *= pix_stride; - - if (xpos < 0) { - xpos = 0; - } - - if (xpos + width > overlay->width) { - width = overlay->width - xpos; - } - - if (ypos + height > overlay->height) { - height = overlay->height - ypos; - } - - destcb += (ypos / 2) * destcb_stride; - destcr += (ypos / 2) * destcr_stride; - - for (i = 0; i < height; i += 2) { - pimage1 = text_image + 4 * (i * overlay->image_width); - pimage2 = pimage1 + 4 * overlay->image_width; - pcb = destcb + (i / 2) * destcb_stride + xpos / 2; - pcr = destcr + (i / 2) * destcr_stride + xpos / 2; - for (j = 0; j < width; j += 2) { - b = pimage1[CAIRO_ARGB_B]; - g = pimage1[CAIRO_ARGB_G]; - r = pimage1[CAIRO_ARGB_R]; - a = pimage1[CAIRO_ARGB_A]; - CAIRO_UNPREMULTIPLY (a, r, g, b); - pimage1 += 4; - - b1 = pimage1[CAIRO_ARGB_B]; - g1 = pimage1[CAIRO_ARGB_G]; - r1 = pimage1[CAIRO_ARGB_R]; - a1 = pimage1[CAIRO_ARGB_A]; - CAIRO_UNPREMULTIPLY (a1, r1, g1, b1); - b += b1; - g += g1; - r += r1; - a += a1; - pimage1 += 4; - - b1 = pimage2[CAIRO_ARGB_B]; - g1 = pimage2[CAIRO_ARGB_G]; - r1 = pimage2[CAIRO_ARGB_R]; - a1 = pimage2[CAIRO_ARGB_A]; - CAIRO_UNPREMULTIPLY (a1, r1, g1, b1); - b += b1; - g += g1; - r += r1; - a += a1; - pimage2 += 4; - - /* + 2 for rounding */ - b1 = pimage2[CAIRO_ARGB_B]; - g1 = pimage2[CAIRO_ARGB_G]; - r1 = pimage2[CAIRO_ARGB_R]; - a1 = pimage2[CAIRO_ARGB_A]; - CAIRO_UNPREMULTIPLY (a1, r1, g1, b1); - b += b1 + 2; - g += g1 + 2; - r += r1 + 2; - a += a1 + 2; - pimage2 += 4; - - b /= 4; - g /= 4; - r /= 4; - a /= 4; - - if (a == 0) { - pcb += pix_stride; - pcr += pix_stride; - continue; - } - COMP_U (cb, r, g, b); - COMP_V (cr, r, g, b); - - x = *pcb; - BLEND (*pcb, a, cb, x); - x = *pcr; - BLEND (*pcr, a, cr, x); - - pcb += pix_stride; - pcr += pix_stride; - } - } -} - -static void -gst_text_overlay_render_pangocairo (GstTextOverlay * overlay, - const gchar * string, gint textlen) -{ - cairo_t *cr; - cairo_surface_t *surface; - PangoRectangle ink_rect, logical_rect; - cairo_matrix_t cairo_matrix; - int width, height; - double scalef = 1.0; - double a, r, g, b; - - g_mutex_lock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock); - - if (overlay->auto_adjust_size) { - /* 640 pixel is default */ - scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS; - } - pango_layout_set_width (overlay->layout, -1); - /* set text on pango layout */ - pango_layout_set_markup (overlay->layout, string, textlen); - - /* get subtitle image size */ - pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect); - - width = (logical_rect.width + overlay->shadow_offset) * scalef; - - if (width + overlay->deltax > - (overlay->use_vertical_render ? overlay->height : overlay->width)) { - /* - * subtitle image width is larger then overlay width - * so rearrange overlay wrap mode. - */ - gst_text_overlay_update_wrap_mode (overlay); - pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect); - width = overlay->width; - } - - height = - (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef; - if (height > overlay->height) { - height = overlay->height; - } - if (overlay->use_vertical_render) { - PangoRectangle rect; - PangoContext *context; - PangoMatrix matrix = PANGO_MATRIX_INIT; - int tmp; - - context = pango_layout_get_context (overlay->layout); - - pango_matrix_rotate (&matrix, -90); - - rect.x = rect.y = 0; - rect.width = width; - rect.height = height; - pango_matrix_transform_pixel_rectangle (&matrix, &rect); - matrix.x0 = -rect.x; - matrix.y0 = -rect.y; - - pango_context_set_matrix (context, &matrix); - - cairo_matrix.xx = matrix.xx; - cairo_matrix.yx = matrix.yx; - cairo_matrix.xy = matrix.xy; - cairo_matrix.yy = matrix.yy; - cairo_matrix.x0 = matrix.x0; - cairo_matrix.y0 = matrix.y0; - cairo_matrix_scale (&cairo_matrix, scalef, scalef); - - tmp = height; - height = width; - width = tmp; - } else { - cairo_matrix_init_scale (&cairo_matrix, scalef, scalef); - } - - g_mutex_unlock (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock); - - /* reallocate surface */ - overlay->text_image = g_realloc (overlay->text_image, 4 * width * height); - - surface = cairo_image_surface_create_for_data (overlay->text_image, - CAIRO_FORMAT_ARGB32, width, height, width * 4); - cr = cairo_create (surface); - - /* clear surface */ - cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); - cairo_paint (cr); - - cairo_set_operator (cr, CAIRO_OPERATOR_OVER); - - if (overlay->want_shading) - cairo_paint_with_alpha (cr, overlay->shading_value); - - /* apply transformations */ - cairo_set_matrix (cr, &cairo_matrix); - - /* FIXME: We use show_layout everywhere except for the surface - * because it's really faster and internally does all kinds of - * caching. Unfortunately we have to paint to a cairo path for - * the outline and this is slow. Once Pango supports user fonts - * we should use them, see - * https://bugzilla.gnome.org/show_bug.cgi?id=598695 - * - * Idea would the be, to create a cairo user font that - * does shadow, outline, text painting in the - * render_glyph function. - */ - - /* draw shadow text */ - cairo_save (cr); - cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset); - cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5); - pango_cairo_show_layout (cr, overlay->layout); - cairo_restore (cr); - - /* draw outline text */ - cairo_save (cr); - cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); - cairo_set_line_width (cr, overlay->outline_offset); - pango_cairo_layout_path (cr, overlay->layout); - cairo_stroke (cr); - cairo_restore (cr); - - a = (overlay->color >> 24) & 0xff; - r = (overlay->color >> 16) & 0xff; - g = (overlay->color >> 8) & 0xff; - b = (overlay->color >> 0) & 0xff; - - /* draw text */ - cairo_save (cr); - cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0); - pango_cairo_show_layout (cr, overlay->layout); - cairo_restore (cr); - - cairo_destroy (cr); - cairo_surface_destroy (surface); - overlay->image_width = width; - overlay->image_height = height; - overlay->baseline_y = ink_rect.y; -} - -#define BOX_XPAD 6 -#define BOX_YPAD 6 - -static inline void -gst_text_overlay_shade_planar_Y (GstTextOverlay * overlay, guchar * dest, - gint x0, gint x1, gint y0, gint y1) -{ - gint i, j, dest_stride; - - dest_stride = gst_video_format_get_row_stride (overlay->format, 0, - overlay->width); - - x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width); - x1 = CLAMP (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] + overlay->shading_value; - - dest[(i * dest_stride) + j] = CLAMP (y, 0, 255); - } - } -} - -static inline void -gst_text_overlay_shade_packed_Y (GstTextOverlay * overlay, guchar * dest, - gint x0, gint x1, gint y0, gint y1) -{ - gint i, j; - guint dest_stride, pixel_stride, component_offset; - - dest_stride = gst_video_format_get_row_stride (overlay->format, 0, - overlay->width); - pixel_stride = gst_video_format_get_pixel_stride (overlay->format, 0); - component_offset = - gst_video_format_get_component_offset (overlay->format, 0, overlay->width, - overlay->height); - - x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width); - x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width); - - y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height); - y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height); - - if (x0 != 0) - x0 = gst_video_format_get_component_width (overlay->format, 0, x0); - if (x1 != 0) - x1 = gst_video_format_get_component_width (overlay->format, 0, x1); - - if (y0 != 0) - y0 = gst_video_format_get_component_height (overlay->format, 0, y0); - if (y1 != 0) - y1 = gst_video_format_get_component_height (overlay->format, 0, y1); - - for (i = y0; i < y1; i++) { - for (j = x0; j < x1; j++) { - gint y; - gint y_pos; - - y_pos = (i * dest_stride) + j * pixel_stride + component_offset; - y = dest[y_pos] + overlay->shading_value; - - dest[y_pos] = CLAMP (y, 0, 255); - } - } -} - -#define gst_text_overlay_shade_BGRx gst_text_overlay_shade_xRGB -#define gst_text_overlay_shade_RGBx gst_text_overlay_shade_xRGB -#define gst_text_overlay_shade_xBGR gst_text_overlay_shade_xRGB -static inline void -gst_text_overlay_shade_xRGB (GstTextOverlay * overlay, guchar * dest, - gint x0, gint x1, gint y0, gint y1) -{ - gint i, j; - - x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width); - x1 = CLAMP (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, y_pos, k; - - y_pos = (i * 4 * overlay->width) + j * 4; - for (k = 0; k < 4; k++) { - y = dest[y_pos + k] + overlay->shading_value; - dest[y_pos + k] = CLAMP (y, 0, 255); - } - } - } -} - -#define ARGB_SHADE_FUNCTION(name, OFFSET) \ -static inline void \ -gst_text_overlay_shade_##name (GstTextOverlay * overlay, guchar * dest, \ -gint x0, gint x1, gint y0, gint y1) \ -{ \ - gint i, j;\ - \ - x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\ - x1 = CLAMP (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, y_pos, k;\ - y_pos = (i * 4 * overlay->width) + j * 4;\ - for (k = OFFSET; k < 3+OFFSET; k++) {\ - y = dest[y_pos + k] + overlay->shading_value;\ - dest[y_pos + k] = CLAMP (y, 0, 255);\ - }\ - }\ - }\ -} -ARGB_SHADE_FUNCTION (ARGB, 1); -ARGB_SHADE_FUNCTION (ABGR, 1); -ARGB_SHADE_FUNCTION (RGBA, 0); -ARGB_SHADE_FUNCTION (BGRA, 0); - - -/* FIXME: - * - use proper strides and offset for I420 - * - don't draw over the edge of the picture (try a longer - * text with a huge font size) - */ - -static inline void -gst_text_overlay_blit_NV12_NV21 (GstTextOverlay * overlay, - guint8 * yuv_pixels, gint xpos, gint ypos) -{ - int y_stride, uv_stride; - int u_offset, v_offset; - int h, w; - - /* because U/V is 2x2 subsampled, we need to round, either up or down, - * to a boundary of integer number of U/V pixels: - */ - xpos = GST_ROUND_UP_2 (xpos); - ypos = GST_ROUND_UP_2 (ypos); - - w = overlay->width; - h = overlay->height; - - y_stride = gst_video_format_get_row_stride (overlay->format, 0, w); - uv_stride = gst_video_format_get_row_stride (overlay->format, 1, w); - u_offset = gst_video_format_get_component_offset (overlay->format, 1, w, h); - v_offset = gst_video_format_get_component_offset (overlay->format, 2, w, h); - - gst_text_overlay_blit_1 (overlay, yuv_pixels, xpos, ypos, overlay->text_image, - y_stride); - gst_text_overlay_blit_sub2x2cbcr (overlay, yuv_pixels + u_offset, - yuv_pixels + v_offset, xpos, ypos, overlay->text_image, uv_stride, - uv_stride, 2); -} - -static inline void -gst_text_overlay_blit_I420 (GstTextOverlay * overlay, - guint8 * yuv_pixels, gint xpos, gint ypos) -{ - int y_stride, u_stride, v_stride; - int u_offset, v_offset; - int h, w; - - /* because U/V is 2x2 subsampled, we need to round, either up or down, - * to a boundary of integer number of U/V pixels: - */ - xpos = GST_ROUND_UP_2 (xpos); - ypos = GST_ROUND_UP_2 (ypos); - - w = overlay->width; - h = overlay->height; - - y_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0, w); - u_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1, w); - v_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 2, w); - u_offset = - gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 1, w, h); - v_offset = - gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 2, w, h); - - gst_text_overlay_blit_1 (overlay, yuv_pixels, xpos, ypos, overlay->text_image, - y_stride); - gst_text_overlay_blit_sub2x2cbcr (overlay, yuv_pixels + u_offset, - yuv_pixels + v_offset, xpos, ypos, overlay->text_image, u_stride, - v_stride, 1); -} - -static inline void -gst_text_overlay_blit_UYVY (GstTextOverlay * overlay, - guint8 * yuv_pixels, gint xpos, gint ypos) -{ - int a0, r0, g0, b0; - int a1, r1, g1, b1; - int y0, y1, u, v; - int i, j; - int h, w; - guchar *pimage, *dest; - - /* because U/V is 2x horizontally subsampled, we need to round to a - * boundary of integer number of U/V pixels in x dimension: - */ - xpos = GST_ROUND_UP_2 (xpos); - - w = overlay->image_width - 2; - h = overlay->image_height - 2; - - if (xpos < 0) { - xpos = 0; - } - - if (xpos + w > overlay->width) { - w = overlay->width - xpos; - } - - if (ypos + h > overlay->height) { - h = overlay->height - ypos; - } - - for (i = 0; i < h; i++) { - pimage = overlay->text_image + i * overlay->image_width * 4; - dest = yuv_pixels + (i + ypos) * overlay->width * 2 + xpos * 2; - for (j = 0; j < w; j += 2) { - b0 = pimage[CAIRO_ARGB_B]; - g0 = pimage[CAIRO_ARGB_G]; - r0 = pimage[CAIRO_ARGB_R]; - a0 = pimage[CAIRO_ARGB_A]; - CAIRO_UNPREMULTIPLY (a0, r0, g0, b0); - pimage += 4; - - b1 = pimage[CAIRO_ARGB_B]; - g1 = pimage[CAIRO_ARGB_G]; - r1 = pimage[CAIRO_ARGB_R]; - a1 = pimage[CAIRO_ARGB_A]; - CAIRO_UNPREMULTIPLY (a1, r1, g1, b1); - pimage += 4; - - a0 += a1 + 2; - a0 /= 2; - if (a0 == 0) { - dest += 4; - continue; - } - - COMP_Y (y0, r0, g0, b0); - COMP_Y (y1, r1, g1, b1); - - b0 += b1 + 2; - g0 += g1 + 2; - r0 += r1 + 2; - - b0 /= 2; - g0 /= 2; - r0 /= 2; - - COMP_U (u, r0, g0, b0); - COMP_V (v, r0, g0, b0); - - BLEND (*dest, a0, u, *dest); - dest++; - BLEND (*dest, a0, y0, *dest); - dest++; - BLEND (*dest, a0, v, *dest); - dest++; - BLEND (*dest, a0, y1, *dest); - dest++; - } - } -} - -static inline void -gst_text_overlay_blit_AYUV (GstTextOverlay * overlay, - guint8 * rgb_pixels, gint xpos, gint ypos) -{ - int a, r, g, b, a1; - int y, u, v; - int i, j; - int h, w; - guchar *pimage, *dest; - - w = overlay->image_width; - h = overlay->image_height; - - if (xpos < 0) { - xpos = 0; - } - - if (xpos + w > overlay->width) { - w = overlay->width - xpos; - } - - if (ypos + h > overlay->height) { - h = overlay->height - ypos; - } - - for (i = 0; i < h; i++) { - pimage = overlay->text_image + i * overlay->image_width * 4; - dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; - for (j = 0; j < w; j++) { - a = pimage[CAIRO_ARGB_A]; - b = pimage[CAIRO_ARGB_B]; - g = pimage[CAIRO_ARGB_G]; - r = pimage[CAIRO_ARGB_R]; - - CAIRO_UNPREMULTIPLY (a, r, g, b); - - // convert background to yuv - COMP_Y (y, r, g, b); - COMP_U (u, r, g, b); - COMP_V (v, r, g, b); - - // preform text "OVER" background alpha compositing - a1 = a + (dest[0] * (255 - a)) / 255 + 1; // add 1 to prevent divide by 0 - OVER (dest[1], a, y, dest[0], dest[1], a1); - OVER (dest[2], a, u, dest[0], dest[2], a1); - OVER (dest[3], a, v, dest[0], dest[3], a1); - dest[0] = a1 - 1; // remove the temporary 1 we added - - pimage += 4; - dest += 4; - } - } -} - -#define xRGB_BLIT_FUNCTION(name, R, G, B) \ -static inline void \ -gst_text_overlay_blit_##name (GstTextOverlay * overlay, \ - guint8 * rgb_pixels, gint xpos, gint ypos) \ -{ \ - int a, r, g, b; \ - int i, j; \ - int h, w; \ - guchar *pimage, *dest; \ - \ - w = overlay->image_width; \ - h = overlay->image_height; \ - \ - if (xpos < 0) { \ - xpos = 0; \ - } \ - \ - if (xpos + w > overlay->width) { \ - w = overlay->width - xpos; \ - } \ - \ - if (ypos + h > overlay->height) { \ - h = overlay->height - ypos; \ - } \ - \ - for (i = 0; i < h; i++) { \ - pimage = overlay->text_image + i * overlay->image_width * 4; \ - dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; \ - for (j = 0; j < w; j++) { \ - a = pimage[CAIRO_ARGB_A]; \ - b = pimage[CAIRO_ARGB_B]; \ - g = pimage[CAIRO_ARGB_G]; \ - r = pimage[CAIRO_ARGB_R]; \ - CAIRO_UNPREMULTIPLY (a, r, g, b); \ - b = (b*a + dest[B] * (255-a)) / 255; \ - g = (g*a + dest[G] * (255-a)) / 255; \ - r = (r*a + dest[R] * (255-a)) / 255; \ - \ - dest[B] = b; \ - dest[G] = g; \ - dest[R] = r; \ - pimage += 4; \ - dest += 4; \ - } \ - } \ -} -xRGB_BLIT_FUNCTION (xRGB, 1, 2, 3); -xRGB_BLIT_FUNCTION (BGRx, 2, 1, 0); -xRGB_BLIT_FUNCTION (xBGR, 3, 2, 1); -xRGB_BLIT_FUNCTION (RGBx, 0, 1, 2); - -#define ARGB_BLIT_FUNCTION(name, A, R, G, B) \ -static inline void \ -gst_text_overlay_blit_##name (GstTextOverlay * overlay, \ - guint8 * rgb_pixels, gint xpos, gint ypos) \ -{ \ - int a, r, g, b, a1; \ - int i, j; \ - int h, w; \ - guchar *pimage, *dest; \ - \ - w = overlay->image_width; \ - h = overlay->image_height; \ - \ - if (xpos < 0) { \ - xpos = 0; \ - } \ - \ - if (xpos + w > overlay->width) { \ - w = overlay->width - xpos; \ - } \ - \ - if (ypos + h > overlay->height) { \ - h = overlay->height - ypos; \ - } \ - \ - for (i = 0; i < h; i++) { \ - pimage = overlay->text_image + i * overlay->image_width * 4; \ - dest = rgb_pixels + (i + ypos) * 4 * overlay->width + xpos * 4; \ - for (j = 0; j < w; j++) { \ - a = pimage[CAIRO_ARGB_A]; \ - b = pimage[CAIRO_ARGB_B]; \ - g = pimage[CAIRO_ARGB_G]; \ - r = pimage[CAIRO_ARGB_R]; \ - CAIRO_UNPREMULTIPLY (a, r, g, b); \ - a1 = a + (dest[A] * (255 - a)) / 255 + 1; \ - OVER (dest[R], a, r, dest[0], dest[R], a1); \ - OVER (dest[G], a, g, dest[0], dest[G], a1); \ - OVER (dest[B], a, b, dest[0], dest[B], a1); \ - dest[A] = a1 - 1; \ - pimage += 4; \ - dest += 4; \ - } \ - } \ -} -ARGB_BLIT_FUNCTION (RGBA, 3, 0, 1, 2); -ARGB_BLIT_FUNCTION (BGRA, 3, 2, 1, 0); -ARGB_BLIT_FUNCTION (ARGB, 0, 1, 2, 3); -ARGB_BLIT_FUNCTION (ABGR, 0, 3, 2, 1); - -static void -gst_text_overlay_render_text (GstTextOverlay * overlay, - const gchar * text, gint textlen) -{ - gchar *string; - - if (!overlay->need_render) { - GST_DEBUG ("Using previously rendered text."); - return; - } - - /* -1 is the whole string */ - if (text != NULL && textlen < 0) { - textlen = strlen (text); - } - - if (text != NULL) { - string = g_strndup (text, textlen); - } else { /* empty string */ - string = g_strdup (" "); - } - g_strdelimit (string, "\r\t", ' '); - textlen = strlen (string); - - /* FIXME: should we check for UTF-8 here? */ - - GST_DEBUG ("Rendering '%s'", string); - gst_text_overlay_render_pangocairo (overlay, string, textlen); - - g_free (string); - - overlay->need_render = FALSE; -} - -static GstFlowReturn -gst_text_overlay_push_frame (GstTextOverlay * overlay, GstBuffer * video_frame) -{ - gint xpos, ypos; - gint width, height; - GstTextOverlayVAlign valign; - GstTextOverlayHAlign halign; - guint8 *data; - gsize size; - - width = overlay->image_width; - height = overlay->image_height; - - video_frame = gst_buffer_make_writable (video_frame); - - data = gst_buffer_map (video_frame, &size, NULL, GST_MAP_WRITE); - - if (overlay->use_vertical_render) - halign = GST_TEXT_OVERLAY_HALIGN_RIGHT; - else - halign = overlay->halign; - - switch (halign) { - case GST_TEXT_OVERLAY_HALIGN_LEFT: - xpos = overlay->xpad; - break; - case GST_TEXT_OVERLAY_HALIGN_CENTER: - xpos = (overlay->width - width) / 2; - break; - case GST_TEXT_OVERLAY_HALIGN_RIGHT: - xpos = overlay->width - width - overlay->xpad; - break; - case GST_TEXT_OVERLAY_HALIGN_POS: - xpos = (gint) (overlay->width * overlay->xpos) - width / 2; - xpos = CLAMP (xpos, 0, overlay->width - width); - if (xpos < 0) - xpos = 0; - break; - default: - xpos = 0; - } - xpos += overlay->deltax; - - if (overlay->use_vertical_render) - valign = GST_TEXT_OVERLAY_VALIGN_TOP; - else - valign = overlay->valign; - - switch (valign) { - case GST_TEXT_OVERLAY_VALIGN_BOTTOM: - ypos = overlay->height - height - overlay->ypad; - break; - case GST_TEXT_OVERLAY_VALIGN_BASELINE: - ypos = overlay->height - (height + overlay->ypad); - break; - case GST_TEXT_OVERLAY_VALIGN_TOP: - ypos = overlay->ypad; - break; - case GST_TEXT_OVERLAY_VALIGN_POS: - ypos = (gint) (overlay->height * overlay->ypos) - height / 2; - ypos = CLAMP (ypos, 0, overlay->height - height); - break; - case GST_TEXT_OVERLAY_VALIGN_CENTER: - ypos = (overlay->height - height) / 2; - break; - default: - ypos = overlay->ypad; - break; - } - ypos += overlay->deltay; - - /* shaded background box */ - if (overlay->want_shading) { - switch (overlay->format) { - case GST_VIDEO_FORMAT_I420: - case GST_VIDEO_FORMAT_NV12: - case GST_VIDEO_FORMAT_NV21: - gst_text_overlay_shade_planar_Y (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_AYUV: - case GST_VIDEO_FORMAT_UYVY: - gst_text_overlay_shade_packed_Y (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_xRGB: - gst_text_overlay_shade_xRGB (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_xBGR: - gst_text_overlay_shade_xBGR (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_BGRx: - gst_text_overlay_shade_BGRx (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_RGBx: - gst_text_overlay_shade_RGBx (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_ARGB: - gst_text_overlay_shade_ARGB (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_ABGR: - gst_text_overlay_shade_ABGR (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_RGBA: - gst_text_overlay_shade_RGBA (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - case GST_VIDEO_FORMAT_BGRA: - gst_text_overlay_shade_BGRA (overlay, data, - xpos, xpos + overlay->image_width, - ypos, ypos + overlay->image_height); - break; - default: - g_assert_not_reached (); - } - } - - if (ypos < 0) - ypos = 0; - - if (overlay->text_image) { - switch (overlay->format) { - case GST_VIDEO_FORMAT_I420: - gst_text_overlay_blit_I420 (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_NV12: - case GST_VIDEO_FORMAT_NV21: - gst_text_overlay_blit_NV12_NV21 (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_UYVY: - gst_text_overlay_blit_UYVY (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_AYUV: - gst_text_overlay_blit_AYUV (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_BGRx: - gst_text_overlay_blit_BGRx (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_xRGB: - gst_text_overlay_blit_xRGB (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_RGBx: - gst_text_overlay_blit_RGBx (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_xBGR: - gst_text_overlay_blit_xBGR (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_ARGB: - gst_text_overlay_blit_ARGB (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_ABGR: - gst_text_overlay_blit_ABGR (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_RGBA: - gst_text_overlay_blit_RGBA (overlay, data, xpos, ypos); - break; - case GST_VIDEO_FORMAT_BGRA: - gst_text_overlay_blit_BGRA (overlay, data, xpos, ypos); - break; - default: - g_assert_not_reached (); - } - } - gst_buffer_unmap (video_frame, data, size); - - return gst_pad_push (overlay->srcpad, video_frame); -} - -static GstPadLinkReturn -gst_text_overlay_text_pad_link (GstPad * pad, GstPad * peer) -{ - GstTextOverlay *overlay; - - overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - - GST_DEBUG_OBJECT (overlay, "Text pad linked"); - - overlay->text_linked = TRUE; - - gst_object_unref (overlay); - - return GST_PAD_LINK_OK; -} - -static void -gst_text_overlay_text_pad_unlink (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"); - - overlay->text_linked = FALSE; - - gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED); -} - -static gboolean -gst_text_overlay_text_event (GstPad * pad, GstEvent * event) -{ - gboolean ret = FALSE; - GstTextOverlay *overlay = NULL; - - overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - - GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_NEWSEGMENT:{ - GstFormat fmt; - gboolean update; - gdouble rate, applied_rate; - gint64 cur, stop, time; - - overlay->text_eos = FALSE; - - gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, - &fmt, &cur, &stop, &time); - - if (fmt == GST_FORMAT_TIME) { - GST_OBJECT_LOCK (overlay); - gst_segment_set_newsegment_full (&overlay->text_segment, update, rate, - applied_rate, GST_FORMAT_TIME, cur, stop, time); - GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT, - &overlay->text_segment); - GST_OBJECT_UNLOCK (overlay); - } else { - GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL), - ("received non-TIME newsegment event on text input")); - } - - gst_event_unref (event); - ret = TRUE; - - /* wake up the video chain, it might be waiting for a text buffer or - * a text segment update */ - GST_OBJECT_LOCK (overlay); - GST_TEXT_OVERLAY_BROADCAST (overlay); - GST_OBJECT_UNLOCK (overlay); - break; - } - case GST_EVENT_FLUSH_STOP: - GST_OBJECT_LOCK (overlay); - GST_INFO_OBJECT (overlay, "text flush stop"); - overlay->text_flushing = FALSE; - overlay->text_eos = FALSE; - gst_text_overlay_pop_text (overlay); - gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME); - GST_OBJECT_UNLOCK (overlay); - gst_event_unref (event); - ret = TRUE; - break; - case GST_EVENT_FLUSH_START: - GST_OBJECT_LOCK (overlay); - GST_INFO_OBJECT (overlay, "text flush start"); - overlay->text_flushing = TRUE; - GST_TEXT_OVERLAY_BROADCAST (overlay); - GST_OBJECT_UNLOCK (overlay); - gst_event_unref (event); - ret = TRUE; - break; - case GST_EVENT_EOS: - GST_OBJECT_LOCK (overlay); - overlay->text_eos = TRUE; - GST_INFO_OBJECT (overlay, "text EOS"); - /* wake up the video chain, it might be waiting for a text buffer or - * a text segment update */ - GST_TEXT_OVERLAY_BROADCAST (overlay); - GST_OBJECT_UNLOCK (overlay); - gst_event_unref (event); - ret = TRUE; - break; - default: - ret = gst_pad_event_default (pad, event); - break; - } - - gst_object_unref (overlay); - - return ret; -} - -static gboolean -gst_text_overlay_video_event (GstPad * pad, GstEvent * event) -{ - gboolean ret = FALSE; - GstTextOverlay *overlay = NULL; - - overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - - GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_NEWSEGMENT: - { - GstFormat format; - gdouble rate; - gint64 start, stop, time; - gboolean update; - - GST_DEBUG_OBJECT (overlay, "received new segment"); - - gst_event_parse_new_segment (event, &update, &rate, &format, &start, - &stop, &time); - - if (format == GST_FORMAT_TIME) { - GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT, - &overlay->segment); - - gst_segment_set_newsegment (&overlay->segment, update, rate, format, - start, stop, time); - } else { - GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL), - ("received non-TIME newsegment event on video input")); - } - - ret = gst_pad_event_default (pad, event); - break; - } - case GST_EVENT_EOS: - GST_OBJECT_LOCK (overlay); - GST_INFO_OBJECT (overlay, "video EOS"); - overlay->video_eos = TRUE; - GST_OBJECT_UNLOCK (overlay); - ret = gst_pad_event_default (pad, event); - break; - case GST_EVENT_FLUSH_START: - GST_OBJECT_LOCK (overlay); - GST_INFO_OBJECT (overlay, "video flush start"); - overlay->video_flushing = TRUE; - GST_TEXT_OVERLAY_BROADCAST (overlay); - GST_OBJECT_UNLOCK (overlay); - ret = gst_pad_event_default (pad, event); - break; - case GST_EVENT_FLUSH_STOP: - GST_OBJECT_LOCK (overlay); - GST_INFO_OBJECT (overlay, "video flush stop"); - overlay->video_flushing = FALSE; - overlay->video_eos = FALSE; - gst_segment_init (&overlay->segment, GST_FORMAT_TIME); - GST_OBJECT_UNLOCK (overlay); - ret = gst_pad_event_default (pad, event); - break; - default: - ret = gst_pad_event_default (pad, event); - break; - } - - gst_object_unref (overlay); - - return ret; -} - -static GstFlowReturn -gst_text_overlay_video_bufferalloc (GstPad * pad, guint64 offset, guint size, - GstCaps * caps, GstBuffer ** buffer) -{ - GstTextOverlay *overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); - GstFlowReturn ret = GST_FLOW_WRONG_STATE; - GstPad *allocpad; - - GST_OBJECT_LOCK (overlay); - allocpad = overlay->srcpad ? gst_object_ref (overlay->srcpad) : NULL; - GST_OBJECT_UNLOCK (overlay); - - if (allocpad) { - ret = gst_pad_alloc_buffer (allocpad, offset, size, caps, buffer); - gst_object_unref (allocpad); - } - - gst_object_unref (overlay); - return ret; -} - -/* Called with lock held */ -static void -gst_text_overlay_pop_text (GstTextOverlay * overlay) -{ - g_return_if_fail (GST_IS_TEXT_OVERLAY (overlay)); - - if (overlay->text_buffer) { - GST_DEBUG_OBJECT (overlay, "releasing text buffer %p", - overlay->text_buffer); - gst_buffer_unref (overlay->text_buffer); - overlay->text_buffer = NULL; - } - - /* Let the text task know we used that buffer */ - GST_TEXT_OVERLAY_BROADCAST (overlay); -} - -/* We receive text buffers here. If they are out of segment we just ignore them. - If the buffer is in our segment we keep it internally except if another one - is already waiting here, in that case we wait that it gets kicked out */ -static GstFlowReturn -gst_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer) -{ - GstFlowReturn ret = GST_FLOW_OK; - GstTextOverlay *overlay = NULL; - gboolean in_seg = FALSE; - gint64 clip_start = 0, clip_stop = 0; - - overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad)); - - GST_OBJECT_LOCK (overlay); - - if (overlay->text_flushing) { - GST_OBJECT_UNLOCK (overlay); - ret = GST_FLOW_WRONG_STATE; - GST_LOG_OBJECT (overlay, "text flushing"); - goto beach; - } - - if (overlay->text_eos) { - GST_OBJECT_UNLOCK (overlay); - ret = GST_FLOW_UNEXPECTED; - GST_LOG_OBJECT (overlay, "text EOS"); - goto beach; - } - - GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%" - GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment, - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) + - GST_BUFFER_DURATION (buffer))); - - if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) { - GstClockTime stop; - - if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer))) - stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer); - else - stop = GST_CLOCK_TIME_NONE; - - in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME, - GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop); - } else { - in_seg = TRUE; - } - - if (in_seg) { - if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) - GST_BUFFER_TIMESTAMP (buffer) = clip_start; - else if (GST_BUFFER_DURATION_IS_VALID (buffer)) - GST_BUFFER_DURATION (buffer) = clip_stop - clip_start; - - /* Wait for the previous buffer to go away */ - while (overlay->text_buffer != NULL) { - GST_DEBUG ("Pad %s:%s has a buffer queued, waiting", - GST_DEBUG_PAD_NAME (pad)); - GST_TEXT_OVERLAY_WAIT (overlay); - GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad)); - if (overlay->text_flushing) { - GST_OBJECT_UNLOCK (overlay); - ret = GST_FLOW_WRONG_STATE; - goto beach; - } - } - - if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) - gst_segment_set_last_stop (&overlay->text_segment, GST_FORMAT_TIME, - clip_start); - - overlay->text_buffer = buffer; - /* That's a new text buffer we need to render */ - overlay->need_render = TRUE; - - /* in case the video chain is waiting for a text buffer, wake it up */ - GST_TEXT_OVERLAY_BROADCAST (overlay); - } - - GST_OBJECT_UNLOCK (overlay); - -beach: - - return ret; -} - -static GstFlowReturn -gst_text_overlay_video_chain (GstPad * pad, GstBuffer * buffer) -{ - GstTextOverlayClass *klass; - GstTextOverlay *overlay; - GstFlowReturn ret = GST_FLOW_OK; - gboolean in_seg = FALSE; - gint64 start, stop, clip_start = 0, clip_stop = 0; - gchar *text = NULL; - - overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad)); - klass = GST_TEXT_OVERLAY_GET_CLASS (overlay); - - if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) - goto missing_timestamp; - - /* ignore buffers that are outside of the current segment */ - start = GST_BUFFER_TIMESTAMP (buffer); - - if (!GST_BUFFER_DURATION_IS_VALID (buffer)) { - stop = GST_CLOCK_TIME_NONE; - } else { - stop = start + GST_BUFFER_DURATION (buffer); - } - - GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%" - GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment, - GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); - - /* segment_clip() will adjust start unconditionally to segment_start if - * no stop time is provided, so handle this ourselves */ - if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start) - goto out_of_segment; - - in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop, - &clip_start, &clip_stop); - - if (!in_seg) - goto out_of_segment; - - /* if the buffer is only partially in the segment, fix up stamps */ - if (clip_start != start || (stop != -1 && clip_stop != stop)) { - GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment"); - buffer = gst_buffer_make_writable (buffer); - GST_BUFFER_TIMESTAMP (buffer) = clip_start; - if (stop != -1) - GST_BUFFER_DURATION (buffer) = clip_stop - clip_start; - } - - /* now, after we've done the clipping, fix up end time if there's no - * duration (we only use those estimated values internally though, we - * don't want to set bogus values on the buffer itself) */ - if (stop == -1) { - GstStructure *s; - gint fps_num, fps_denom; - - s = gst_caps_get_structure (GST_PAD_CAPS (pad), 0); - if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) && - fps_num && fps_denom) { - GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate"); - stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num); - } else { - GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration"); - stop = start + 1; /* we need to assume some interval */ - } - } - - gst_object_sync_values (G_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer)); - -wait_for_text_buf: - - GST_OBJECT_LOCK (overlay); - - if (overlay->video_flushing) - goto flushing; - - if (overlay->video_eos) - goto have_eos; - - if (overlay->silent) { - GST_OBJECT_UNLOCK (overlay); - ret = gst_pad_push (overlay->srcpad, buffer); - - /* Update last_stop */ - gst_segment_set_last_stop (&overlay->segment, GST_FORMAT_TIME, clip_start); - - return ret; - } - - /* Text pad not linked, rendering internal text */ - if (!overlay->text_linked) { - if (klass->get_text) { - text = klass->get_text (overlay, buffer); - } else { - text = g_strdup (overlay->default_text); - } - - GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default " - "text: '%s'", GST_STR_NULL (text)); - - GST_OBJECT_UNLOCK (overlay); - - if (text != NULL && *text != '\0') { - /* Render and push */ - gst_text_overlay_render_text (overlay, text, -1); - ret = gst_text_overlay_push_frame (overlay, buffer); - } else { - /* Invalid or empty string */ - ret = gst_pad_push (overlay->srcpad, buffer); - } - } else { - /* Text pad linked, check if we have a text buffer queued */ - if (overlay->text_buffer) { - gboolean pop_text = FALSE, valid_text_time = TRUE; - GstClockTime text_start = GST_CLOCK_TIME_NONE; - GstClockTime text_end = GST_CLOCK_TIME_NONE; - GstClockTime text_running_time = GST_CLOCK_TIME_NONE; - GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE; - GstClockTime vid_running_time, vid_running_time_end; - - /* 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_IS_VALID (overlay->text_buffer) || - !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) { - GST_WARNING_OBJECT (overlay, - "Got text buffer with invalid timestamp or duration"); - pop_text = TRUE; - valid_text_time = FALSE; - } else { - text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer); - text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer); - } - - vid_running_time = - gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, - start); - vid_running_time_end = - gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, - stop); - - /* If timestamp and duration are valid */ - if (valid_text_time) { - text_running_time = - gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, - text_start); - text_running_time_end = - gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, - text_end); - } - - GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, - GST_TIME_ARGS (text_running_time), - GST_TIME_ARGS (text_running_time_end)); - GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, - GST_TIME_ARGS (vid_running_time), - GST_TIME_ARGS (vid_running_time_end)); - - /* Text too old or in the future */ - if (valid_text_time && text_running_time_end <= vid_running_time) { - /* text buffer too old, get rid of it and do nothing */ - GST_LOG_OBJECT (overlay, "text buffer too old, popping"); - pop_text = FALSE; - gst_text_overlay_pop_text (overlay); - GST_OBJECT_UNLOCK (overlay); - goto wait_for_text_buf; - } else if (valid_text_time && vid_running_time_end <= text_running_time) { - GST_LOG_OBJECT (overlay, "text in future, pushing video buf"); - GST_OBJECT_UNLOCK (overlay); - /* Push the video frame */ - ret = gst_pad_push (overlay->srcpad, buffer); - } else { - gchar *in_text, *otext; - gsize in_size, osize; - - otext = - gst_buffer_map (overlay->text_buffer, &osize, NULL, GST_MAP_READ); - in_text = otext; - in_size = osize; - - /* g_markup_escape_text() absolutely requires valid UTF8 input, it - * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING - * here on purpose, this is something that needs fixing upstream */ - if (!g_utf8_validate (in_text, in_size, NULL)) { - const gchar *end = NULL; - - GST_WARNING_OBJECT (overlay, "received invalid UTF-8"); - in_text = g_strndup (in_text, in_size); - while (!g_utf8_validate (in_text, in_size, &end) && end) - *((gchar *) end) = '*'; - } - - /* Get the string */ - if (overlay->have_pango_markup) { - text = g_strndup (in_text, in_size); - } else { - text = g_markup_escape_text (in_text, in_size); - } - - if (text != NULL && *text != '\0') { - gint text_len = strlen (text); - - while (text_len > 0 && (text[text_len - 1] == '\n' || - text[text_len - 1] == '\r')) { - --text_len; - } - GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text); - gst_text_overlay_render_text (overlay, text, text_len); - } else { - GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)"); - gst_text_overlay_render_text (overlay, " ", 1); - } - gst_buffer_unmap (overlay->text_buffer, otext, osize); - - if (in_text != otext) - g_free (in_text); - - GST_OBJECT_UNLOCK (overlay); - ret = gst_text_overlay_push_frame (overlay, buffer); - - if (valid_text_time && text_running_time_end <= vid_running_time_end) { - GST_LOG_OBJECT (overlay, "text buffer not needed any longer"); - pop_text = TRUE; - } - } - if (pop_text) { - GST_OBJECT_LOCK (overlay); - gst_text_overlay_pop_text (overlay); - GST_OBJECT_UNLOCK (overlay); - } - } else { - gboolean wait_for_text_buf = TRUE; - - if (overlay->text_eos) - wait_for_text_buf = FALSE; - - if (!overlay->wait_text) - wait_for_text_buf = FALSE; - - /* Text pad linked, but no text buffer available - what now? */ - if (overlay->text_segment.format == GST_FORMAT_TIME) { - GstClockTime text_start_running_time, text_last_stop_running_time; - GstClockTime vid_running_time; - - vid_running_time = - gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, - GST_BUFFER_TIMESTAMP (buffer)); - text_start_running_time = - gst_segment_to_running_time (&overlay->text_segment, - GST_FORMAT_TIME, overlay->text_segment.start); - text_last_stop_running_time = - gst_segment_to_running_time (&overlay->text_segment, - GST_FORMAT_TIME, overlay->text_segment.last_stop); - - if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) && - vid_running_time < text_start_running_time) || - (GST_CLOCK_TIME_IS_VALID (text_last_stop_running_time) && - vid_running_time < text_last_stop_running_time)) { - wait_for_text_buf = FALSE; - } - } - - if (wait_for_text_buf) { - GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one"); - GST_TEXT_OVERLAY_WAIT (overlay); - GST_DEBUG_OBJECT (overlay, "resuming"); - GST_OBJECT_UNLOCK (overlay); - goto wait_for_text_buf; - } else { - GST_OBJECT_UNLOCK (overlay); - GST_LOG_OBJECT (overlay, "no need to wait for a text buffer"); - ret = gst_pad_push (overlay->srcpad, buffer); - } - } - } - - g_free (text); - - /* Update last_stop */ - gst_segment_set_last_stop (&overlay->segment, GST_FORMAT_TIME, clip_start); - - return ret; - -missing_timestamp: - { - GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding"); - gst_buffer_unref (buffer); - return GST_FLOW_OK; - } - -flushing: - { - GST_OBJECT_UNLOCK (overlay); - GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer"); - gst_buffer_unref (buffer); - return GST_FLOW_WRONG_STATE; - } -have_eos: - { - GST_OBJECT_UNLOCK (overlay); - GST_DEBUG_OBJECT (overlay, "eos, discarding buffer"); - gst_buffer_unref (buffer); - return GST_FLOW_UNEXPECTED; - } -out_of_segment: - { - GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding"); - gst_buffer_unref (buffer); - return GST_FLOW_OK; - } -} - -static GstStateChangeReturn -gst_text_overlay_change_state (GstElement * element, GstStateChange transition) -{ - GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; - GstTextOverlay *overlay = GST_TEXT_OVERLAY (element); - - switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_READY: - GST_OBJECT_LOCK (overlay); - overlay->text_flushing = TRUE; - overlay->video_flushing = TRUE; - /* pop_text will broadcast on the GCond and thus also make the video - * chain exit if it's waiting for a text buffer */ - gst_text_overlay_pop_text (overlay); - GST_OBJECT_UNLOCK (overlay); - break; - default: - break; - } - - ret = parent_class->change_state (element, transition); - if (ret == GST_STATE_CHANGE_FAILURE) - return ret; - - switch (transition) { - case GST_STATE_CHANGE_READY_TO_PAUSED: - GST_OBJECT_LOCK (overlay); - overlay->text_flushing = FALSE; - overlay->video_flushing = FALSE; - overlay->video_eos = FALSE; - overlay->text_eos = FALSE; - gst_segment_init (&overlay->segment, GST_FORMAT_TIME); - gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME); - GST_OBJECT_UNLOCK (overlay); - break; - default: - break; - } - - return ret; -} - -static gboolean -plugin_init (GstPlugin * plugin) -{ - gst_controller_init (NULL, NULL); - - if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE, - GST_TYPE_TEXT_OVERLAY) || - !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE, - GST_TYPE_TIME_OVERLAY) || - !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE, - GST_TYPE_CLOCK_OVERLAY) || - !gst_element_register (plugin, "textrender", GST_RANK_NONE, - GST_TYPE_TEXT_RENDER)) { - return FALSE; - } - - /*texttestsrc_plugin_init(module, plugin); */ - - GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements"); - - return TRUE; -} - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, - "pango", "Pango-based text rendering and overlay", plugin_init, - VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/pango/gsttextoverlay.h b/ext/pango/gsttextoverlay.h index 5cd42601d4..c1dab86dcb 100644 --- a/ext/pango/gsttextoverlay.h +++ b/ext/pango/gsttextoverlay.h @@ -1,171 +1,60 @@ +/* GStreamer + * Copyright (C) 2011 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + #ifndef __GST_TEXT_OVERLAY_H__ #define __GST_TEXT_OVERLAY_H__ -#include -#include -#include -#include +#include "gstbasetextoverlay.h" G_BEGIN_DECLS -#define GST_TYPE_TEXT_OVERLAY (gst_text_overlay_get_type()) -#define GST_TEXT_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ - GST_TYPE_TEXT_OVERLAY, GstTextOverlay)) -#define GST_TEXT_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\ - GST_TYPE_TEXT_OVERLAY,GstTextOverlayClass)) -#define GST_TEXT_OVERLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ - GST_TYPE_TEXT_OVERLAY, GstTextOverlayClass)) -#define GST_IS_TEXT_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\ - GST_TYPE_TEXT_OVERLAY)) -#define GST_IS_TEXT_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),\ - GST_TYPE_TEXT_OVERLAY)) +#define GST_TYPE_TEXT_OVERLAY \ + (gst_text_overlay_get_type()) +#define GST_TEXT_OVERLAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TEXT_OVERLAY,GstTextOverlay)) +#define GST_TEXT_OVERLAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TEXT_OVERLAY,GstTextOverlayClass)) +#define GST_IS_TEXT_OVERLAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TEXT_OVERLAY)) +#define GST_IS_TEXT_OVERLAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TEXT_OVERLAY)) -typedef struct _GstTextOverlay GstTextOverlay; +typedef struct _GstTextOverlay GstTextOverlay; typedef struct _GstTextOverlayClass GstTextOverlayClass; -/** - * GstTextOverlayVAlign: - * @GST_TEXT_OVERLAY_VALIGN_BASELINE: draw text on the baseline - * @GST_TEXT_OVERLAY_VALIGN_BOTTOM: draw text on the bottom - * @GST_TEXT_OVERLAY_VALIGN_TOP: draw text on top - * @GST_TEXT_OVERLAY_VALIGN_POS: draw text according to the #GstTextOverlay:ypos property - * @GST_TEXT_OVERLAY_VALIGN_CENTER: draw text vertically centered - * - * Vertical alignment of the text. - */ -typedef enum { - GST_TEXT_OVERLAY_VALIGN_BASELINE, - GST_TEXT_OVERLAY_VALIGN_BOTTOM, - GST_TEXT_OVERLAY_VALIGN_TOP, - GST_TEXT_OVERLAY_VALIGN_POS, - GST_TEXT_OVERLAY_VALIGN_CENTER -} GstTextOverlayVAlign; - -/** - * GstTextOverlayHAlign: - * @GST_TEXT_OVERLAY_HALIGN_LEFT: align text left - * @GST_TEXT_OVERLAY_HALIGN_CENTER: align text center - * @GST_TEXT_OVERLAY_HALIGN_RIGHT: align text right - * @GST_TEXT_OVERLAY_HALIGN_POS: position text according to the #GstTextOverlay:xpos property - * - * Horizontal alignment of the text. - */ -/* FIXME 0.11: remove GST_TEXT_OVERLAY_HALIGN_UNUSED */ -typedef enum { - GST_TEXT_OVERLAY_HALIGN_LEFT, - GST_TEXT_OVERLAY_HALIGN_CENTER, - GST_TEXT_OVERLAY_HALIGN_RIGHT, - GST_TEXT_OVERLAY_HALIGN_UNUSED, - GST_TEXT_OVERLAY_HALIGN_POS -} GstTextOverlayHAlign; - -/** - * GstTextOverlayWrapMode: - * @GST_TEXT_OVERLAY_WRAP_MODE_NONE: no wrapping - * @GST_TEXT_OVERLAY_WRAP_MODE_WORD: do word wrapping - * @GST_TEXT_OVERLAY_WRAP_MODE_CHAR: do char wrapping - * @GST_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR: do word and char wrapping - * - * Whether to wrap the text and if so how. - */ -typedef enum { - GST_TEXT_OVERLAY_WRAP_MODE_NONE = -1, - GST_TEXT_OVERLAY_WRAP_MODE_WORD = PANGO_WRAP_WORD, - GST_TEXT_OVERLAY_WRAP_MODE_CHAR = PANGO_WRAP_CHAR, - GST_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR = PANGO_WRAP_WORD_CHAR -} GstTextOverlayWrapMode; - -/** - * GstTextOverlayLineAlign: - * @GST_TEXT_OVERLAY_LINE_ALIGN_LEFT: lines are left-aligned - * @GST_TEXT_OVERLAY_LINE_ALIGN_CENTER: lines are center-aligned - * @GST_TEXT_OVERLAY_LINE_ALIGN_RIGHT: lines are right-aligned - * - * Alignment of text lines relative to each other - */ -typedef enum { - GST_TEXT_OVERLAY_LINE_ALIGN_LEFT = PANGO_ALIGN_LEFT, - GST_TEXT_OVERLAY_LINE_ALIGN_CENTER = PANGO_ALIGN_CENTER, - GST_TEXT_OVERLAY_LINE_ALIGN_RIGHT = PANGO_ALIGN_RIGHT -} GstTextOverlayLineAlign; - /** * GstTextOverlay: * - * Opaque textoverlay object structure + * Opaque textoverlay data structure. */ struct _GstTextOverlay { - GstElement element; - - GstPad *video_sinkpad; - GstPad *text_sinkpad; - GstPad *srcpad; - - GstSegment segment; - GstSegment text_segment; - GstBuffer *text_buffer; - gboolean text_linked; - gboolean video_flushing; - gboolean video_eos; - gboolean text_flushing; - gboolean text_eos; - - GCond *cond; /* to signal removal of a queued text - * buffer, arrival of a text buffer, - * a text segment update, or a change - * in status (e.g. shutdown, flushing) */ - - gint width; - gint height; - gint fps_n; - gint fps_d; - GstVideoFormat format; - - GstTextOverlayVAlign valign; - GstTextOverlayHAlign halign; - GstTextOverlayWrapMode wrap_mode; - GstTextOverlayLineAlign line_align; - - gint xpad; - gint ypad; - gint deltax; - gint deltay; - gdouble xpos; - gdouble ypos; - gchar *default_text; - gboolean want_shading; - gboolean silent; - gboolean wait_text; - guint color; - - PangoLayout *layout; - gdouble shadow_offset; - gdouble outline_offset; - guchar *text_image; - gint image_width; - gint image_height; - gint baseline_y; - - gboolean auto_adjust_size; - gboolean need_render; - - gint shading_value; /* for timeoverlay subclass */ - - gboolean have_pango_markup; - gboolean use_vertical_render; + GstBaseTextOverlay parent; }; struct _GstTextOverlayClass { - GstElementClass parent_class; - - PangoContext *pango_context; - GMutex *pango_lock; - - gchar * (*get_text) (GstTextOverlay *overlay, GstBuffer *video_frame); + GstBaseTextOverlayClass parent_class; }; -GType gst_text_overlay_get_type(void) G_GNUC_CONST; +GType gst_text_overlay_get_type (void); G_END_DECLS -#endif /* __GST_TEXT_OVERLAY_H */ +#endif /* __GST_TEXT_OVERLAY_H__ */ + diff --git a/ext/pango/gsttimeoverlay.c b/ext/pango/gsttimeoverlay.c index a342a51ed4..ab2bca0c97 100644 --- a/ext/pango/gsttimeoverlay.c +++ b/ext/pango/gsttimeoverlay.c @@ -20,11 +20,11 @@ /** * SECTION:element-timeoverlay - * @see_also: #GstTextOverlay, #GstClockOverlay + * @see_also: #GstBaseTextOverlay, #GstClockOverlay * * This element overlays the buffer time stamps of a video stream on * top of itself. You can position the text and configure the font details - * using the properties of the #GstTextOverlay class. By default, the + * using the properties of the #GstBaseTextOverlay class. By default, the * time stamp is displayed in the top left corner of the picture, with some * padding to the left and to the top. * @@ -50,8 +50,8 @@ #include -GST_BOILERPLATE (GstTimeOverlay, gst_time_overlay, GstTextOverlay, - GST_TYPE_TEXT_OVERLAY); +GST_BOILERPLATE (GstTimeOverlay, gst_time_overlay, GstBaseTextOverlay, + GST_TYPE_BASE_TEXT_OVERLAY); static void gst_time_overlay_base_init (gpointer g_class) @@ -82,7 +82,8 @@ gst_time_overlay_render_time (GstTimeOverlay * overlay, GstClockTime time) /* Called with lock held */ static gchar * -gst_time_overlay_get_text (GstTextOverlay * overlay, GstBuffer * video_frame) +gst_time_overlay_get_text (GstBaseTextOverlay * overlay, + GstBuffer * video_frame) { GstClockTime time = GST_BUFFER_TIMESTAMP (video_frame); gchar *time_str, *txt, *ret; @@ -115,9 +116,9 @@ gst_time_overlay_get_text (GstTextOverlay * overlay, GstBuffer * video_frame) static void gst_time_overlay_class_init (GstTimeOverlayClass * klass) { - GstTextOverlayClass *gsttextoverlay_class; + GstBaseTextOverlayClass *gsttextoverlay_class; - gsttextoverlay_class = (GstTextOverlayClass *) klass; + gsttextoverlay_class = (GstBaseTextOverlayClass *) klass; gsttextoverlay_class->get_text = gst_time_overlay_get_text; } @@ -126,12 +127,12 @@ static void gst_time_overlay_init (GstTimeOverlay * overlay, GstTimeOverlayClass * klass) { PangoFontDescription *font_description; - GstTextOverlay *textoverlay; + GstBaseTextOverlay *textoverlay; PangoContext *context; - textoverlay = GST_TEXT_OVERLAY (overlay); + textoverlay = GST_BASE_TEXT_OVERLAY (overlay); - context = GST_TEXT_OVERLAY_CLASS (klass)->pango_context; + context = GST_BASE_TEXT_OVERLAY_CLASS (klass)->pango_context; pango_context_set_language (context, pango_language_from_string ("en_US")); pango_context_set_base_dir (context, PANGO_DIRECTION_LTR); @@ -146,6 +147,6 @@ gst_time_overlay_init (GstTimeOverlay * overlay, GstTimeOverlayClass * klass) pango_context_set_font_description (context, font_description); pango_font_description_free (font_description); - textoverlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP; - textoverlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT; + textoverlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP; + textoverlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_LEFT; } diff --git a/ext/pango/gsttimeoverlay.h b/ext/pango/gsttimeoverlay.h index 5fbfba9712..dbce8ffc37 100644 --- a/ext/pango/gsttimeoverlay.h +++ b/ext/pango/gsttimeoverlay.h @@ -22,7 +22,7 @@ #ifndef __GST_TIME_OVERLAY_H__ #define __GST_TIME_OVERLAY_H__ -#include "gsttextoverlay.h" +#include "gstbasetextoverlay.h" G_BEGIN_DECLS @@ -46,11 +46,11 @@ typedef struct _GstTimeOverlayClass GstTimeOverlayClass; * Opaque timeoverlay data structure. */ struct _GstTimeOverlay { - GstTextOverlay textoverlay; + GstBaseTextOverlay textoverlay; }; struct _GstTimeOverlayClass { - GstTextOverlayClass parent_class; + GstBaseTextOverlayClass parent_class; }; GType gst_time_overlay_get_type (void);