1 /* GStreamer
2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) <2003> David Schleef <ds@schleef.org>
4 * Copyright (C) <2006> Julien Moutte <julien@moutte.net>
5 * Copyright (C) <2006> Zeeshan Ali <zeeshan.ali@nokia.com>
6 * Copyright (C) <2006-2008> Tim-Philipp Müller <tim centricular net>
7 * Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <gst/video/video.h>
30 #include <gst/video/gstvideometa.h>
31
32 #include "gstbasetextoverlay.h"
33 #include "gsttextoverlay.h"
34 #include "gsttimeoverlay.h"
35 #include "gstclockoverlay.h"
36 #include "gsttextrender.h"
37 #include <string.h>
38 #include <math.h>
39
40 /* FIXME:
41 * - use proper strides and offset for I420
42 * - if text is wider than the video picture, it does not get
43 * clipped properly during blitting (if wrapping is disabled)
44 */
45
46 GST_DEBUG_CATEGORY (pango_debug);
47 #define GST_CAT_DEFAULT pango_debug
48
49 #define DEFAULT_PROP_TEXT ""
50 #define DEFAULT_PROP_SHADING FALSE
51 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
52 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
53 #define DEFAULT_PROP_XPAD 25
54 #define DEFAULT_PROP_YPAD 25
55 #define DEFAULT_PROP_DELTAX 0
56 #define DEFAULT_PROP_DELTAY 0
57 #define DEFAULT_PROP_XPOS 0.5
58 #define DEFAULT_PROP_YPOS 0.5
59 #define DEFAULT_PROP_WRAP_MODE GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
60 #define DEFAULT_PROP_FONT_DESC ""
61 #define DEFAULT_PROP_SILENT FALSE
62 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
63 #define DEFAULT_PROP_WAIT_TEXT TRUE
64 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
65 #define DEFAULT_PROP_VERTICAL_RENDER FALSE
66 #define DEFAULT_PROP_SCALE_MODE GST_BASE_TEXT_OVERLAY_SCALE_MODE_NONE
67 #define DEFAULT_PROP_SCALE_PAR_N 1
68 #define DEFAULT_PROP_SCALE_PAR_D 1
69 #define DEFAULT_PROP_DRAW_SHADOW TRUE
70 #define DEFAULT_PROP_DRAW_OUTLINE TRUE
71 #define DEFAULT_PROP_COLOR 0xffffffff
72 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
73 #define DEFAULT_PROP_SHADING_VALUE 80
74 #define DEFAULT_PROP_TEXT_X 0
75 #define DEFAULT_PROP_TEXT_Y 0
76 #define DEFAULT_PROP_TEXT_WIDTH 1
77 #define DEFAULT_PROP_TEXT_HEIGHT 1
78
79 #define MINIMUM_OUTLINE_OFFSET 1.0
80 #define DEFAULT_SCALE_BASIS 640
81
82 enum
83 {
84 PROP_0,
85 PROP_TEXT,
86 PROP_SHADING,
87 PROP_SHADING_VALUE,
88 PROP_HALIGNMENT,
89 PROP_VALIGNMENT,
90 PROP_XPAD,
91 PROP_YPAD,
92 PROP_DELTAX,
93 PROP_DELTAY,
94 PROP_XPOS,
95 PROP_YPOS,
96 PROP_X_ABSOLUTE,
97 PROP_Y_ABSOLUTE,
98 PROP_WRAP_MODE,
99 PROP_FONT_DESC,
100 PROP_SILENT,
101 PROP_LINE_ALIGNMENT,
102 PROP_WAIT_TEXT,
103 PROP_AUTO_ADJUST_SIZE,
104 PROP_VERTICAL_RENDER,
105 PROP_SCALE_MODE,
106 PROP_SCALE_PAR,
107 PROP_COLOR,
108 PROP_DRAW_SHADOW,
109 PROP_DRAW_OUTLINE,
110 PROP_OUTLINE_COLOR,
111 PROP_TEXT_X,
112 PROP_TEXT_Y,
113 PROP_TEXT_WIDTH,
114 PROP_TEXT_HEIGHT,
115 PROP_LAST
116 };
117
118 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
119
120 #define BASE_TEXT_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
121
122 #define BASE_TEXT_OVERLAY_ALL_CAPS BASE_TEXT_OVERLAY_CAPS ";" \
123 GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
124
125 static GstStaticCaps sw_template_caps =
126 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
127
128 static GstStaticPadTemplate src_template_factory =
129 GST_STATIC_PAD_TEMPLATE ("src",
130 GST_PAD_SRC,
131 GST_PAD_ALWAYS,
132 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
133 );
134
135 static GstStaticPadTemplate video_sink_template_factory =
136 GST_STATIC_PAD_TEMPLATE ("video_sink",
137 GST_PAD_SINK,
138 GST_PAD_ALWAYS,
139 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
140 );
141
142 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
143 static GType
gst_base_text_overlay_valign_get_type(void)144 gst_base_text_overlay_valign_get_type (void)
145 {
146 static GType base_text_overlay_valign_type = 0;
147 static const GEnumValue base_text_overlay_valign[] = {
148 {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
149 {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
150 {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
151 {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position",
152 "Absolute position clamped to canvas"},
153 {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
154 {GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE, "absolute", "Absolute position"},
155 {0, NULL, NULL},
156 };
157
158 if (!base_text_overlay_valign_type) {
159 base_text_overlay_valign_type =
160 g_enum_register_static ("GstBaseTextOverlayVAlign",
161 base_text_overlay_valign);
162 }
163 return base_text_overlay_valign_type;
164 }
165
166 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
167 static GType
gst_base_text_overlay_halign_get_type(void)168 gst_base_text_overlay_halign_get_type (void)
169 {
170 static GType base_text_overlay_halign_type = 0;
171 static const GEnumValue base_text_overlay_halign[] = {
172 {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
173 {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
174 {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
175 {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position",
176 "Absolute position clamped to canvas"},
177 {GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE, "absolute", "Absolute position"},
178 {0, NULL, NULL},
179 };
180
181 if (!base_text_overlay_halign_type) {
182 base_text_overlay_halign_type =
183 g_enum_register_static ("GstBaseTextOverlayHAlign",
184 base_text_overlay_halign);
185 }
186 return base_text_overlay_halign_type;
187 }
188
189
190 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
191 static GType
gst_base_text_overlay_wrap_mode_get_type(void)192 gst_base_text_overlay_wrap_mode_get_type (void)
193 {
194 static GType base_text_overlay_wrap_mode_type = 0;
195 static const GEnumValue base_text_overlay_wrap_mode[] = {
196 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
197 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
198 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
199 {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
200 {0, NULL, NULL},
201 };
202
203 if (!base_text_overlay_wrap_mode_type) {
204 base_text_overlay_wrap_mode_type =
205 g_enum_register_static ("GstBaseTextOverlayWrapMode",
206 base_text_overlay_wrap_mode);
207 }
208 return base_text_overlay_wrap_mode_type;
209 }
210
211 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
212 static GType
gst_base_text_overlay_line_align_get_type(void)213 gst_base_text_overlay_line_align_get_type (void)
214 {
215 static GType base_text_overlay_line_align_type = 0;
216 static const GEnumValue base_text_overlay_line_align[] = {
217 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
218 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
219 {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
220 {0, NULL, NULL}
221 };
222
223 if (!base_text_overlay_line_align_type) {
224 base_text_overlay_line_align_type =
225 g_enum_register_static ("GstBaseTextOverlayLineAlign",
226 base_text_overlay_line_align);
227 }
228 return base_text_overlay_line_align_type;
229 }
230
231 #define GST_TYPE_BASE_TEXT_OVERLAY_SCALE_MODE (gst_base_text_overlay_scale_mode_get_type())
232 static GType
gst_base_text_overlay_scale_mode_get_type(void)233 gst_base_text_overlay_scale_mode_get_type (void)
234 {
235 static GType base_text_overlay_scale_mode_type = 0;
236 static const GEnumValue base_text_overlay_scale_mode[] = {
237 {GST_BASE_TEXT_OVERLAY_SCALE_MODE_NONE, "none", "none"},
238 {GST_BASE_TEXT_OVERLAY_SCALE_MODE_PAR, "par", "par"},
239 {GST_BASE_TEXT_OVERLAY_SCALE_MODE_DISPLAY, "display", "display"},
240 {GST_BASE_TEXT_OVERLAY_SCALE_MODE_USER, "user", "user"},
241 {0, NULL, NULL}
242 };
243
244 if (!base_text_overlay_scale_mode_type) {
245 base_text_overlay_scale_mode_type =
246 g_enum_register_static ("GstBaseTextOverlayScaleMode",
247 base_text_overlay_scale_mode);
248 }
249 return base_text_overlay_scale_mode_type;
250 }
251
252 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
253 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
254 #define GST_BASE_TEXT_OVERLAY_LOCK(ov) (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
255 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
256 #define GST_BASE_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
257 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
258 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
259
260 static GstElementClass *parent_class = NULL;
261 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
262 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
263 GstBaseTextOverlayClass * klass);
264
265 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
266 element, GstStateChange transition);
267
268 static GstCaps *gst_base_text_overlay_get_videosink_caps (GstPad * pad,
269 GstBaseTextOverlay * overlay, GstCaps * filter);
270 static GstCaps *gst_base_text_overlay_get_src_caps (GstPad * pad,
271 GstBaseTextOverlay * overlay, GstCaps * filter);
272 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
273 GstCaps * caps);
274 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
275 GstCaps * caps);
276 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
277 GstObject * parent, GstEvent * event);
278 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
279 GstObject * parent, GstQuery * query);
280
281 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
282 GstObject * parent, GstEvent * event);
283 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
284 GstObject * parent, GstQuery * query);
285 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
286 GstObject * parent, GstBuffer * buffer);
287
288 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
289 GstObject * parent, GstEvent * event);
290 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
291 GstObject * parent, GstBuffer * buffer);
292 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
293 GstObject * parent, GstPad * peer);
294 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
295 GstObject * parent);
296 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
297
298 static void gst_base_text_overlay_finalize (GObject * object);
299 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
300 const GValue * value, GParamSpec * pspec);
301 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
302 GValue * value, GParamSpec * pspec);
303
304 static void
305 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
306 PangoFontDescription * desc);
307 static gboolean gst_base_text_overlay_can_handle_caps (GstCaps * incaps);
308
309 static void
310 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay);
311
312 GType
gst_base_text_overlay_get_type(void)313 gst_base_text_overlay_get_type (void)
314 {
315 static GType type = 0;
316
317 if (g_once_init_enter ((gsize *) & type)) {
318 static const GTypeInfo info = {
319 sizeof (GstBaseTextOverlayClass),
320 (GBaseInitFunc) NULL,
321 NULL,
322 (GClassInitFunc) gst_base_text_overlay_class_init,
323 NULL,
324 NULL,
325 sizeof (GstBaseTextOverlay),
326 0,
327 (GInstanceInitFunc) gst_base_text_overlay_init,
328 };
329
330 g_once_init_leave ((gsize *) & type,
331 g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
332 0));
333 }
334
335 return type;
336 }
337
338 static gchar *
gst_base_text_overlay_get_text(GstBaseTextOverlay * overlay,GstBuffer * video_frame)339 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
340 GstBuffer * video_frame)
341 {
342 return g_strdup (overlay->default_text);
343 }
344
345 static void
gst_base_text_overlay_class_init(GstBaseTextOverlayClass * klass)346 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
347 {
348 GObjectClass *gobject_class;
349 GstElementClass *gstelement_class;
350
351 gobject_class = (GObjectClass *) klass;
352 gstelement_class = (GstElementClass *) klass;
353
354 parent_class = g_type_class_peek_parent (klass);
355
356 gobject_class->finalize = gst_base_text_overlay_finalize;
357 gobject_class->set_property = gst_base_text_overlay_set_property;
358 gobject_class->get_property = gst_base_text_overlay_get_property;
359
360 gst_element_class_add_static_pad_template (gstelement_class,
361 &src_template_factory);
362 gst_element_class_add_static_pad_template (gstelement_class,
363 &video_sink_template_factory);
364
365 gstelement_class->change_state =
366 GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
367
368 klass->get_text = gst_base_text_overlay_get_text;
369
370 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
371 g_param_spec_string ("text", "text",
372 "Text to be display.", DEFAULT_PROP_TEXT,
373 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
374 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
375 g_param_spec_boolean ("shaded-background", "shaded background",
376 "Whether to shade the background under the text area",
377 DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
378 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING_VALUE,
379 g_param_spec_uint ("shading-value", "background shading value",
380 "Shading value to apply if shaded-background is true", 1, 255,
381 DEFAULT_PROP_SHADING_VALUE,
382 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
383 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
384 g_param_spec_enum ("valignment", "vertical alignment",
385 "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
386 DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
387 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
388 g_param_spec_enum ("halignment", "horizontal alignment",
389 "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
390 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
391 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
392 g_param_spec_int ("xpad", "horizontal paddding",
393 "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
394 DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
395 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
396 g_param_spec_int ("ypad", "vertical padding",
397 "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
398 DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
399 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
400 g_param_spec_int ("deltax", "X position modifier",
401 "Shift X position to the left or to the right. Unit is pixels.",
402 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
403 GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
404 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
405 g_param_spec_int ("deltay", "Y position modifier",
406 "Shift Y position up or down. Unit is pixels.",
407 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAY,
408 GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
409
410 /**
411 * GstBaseTextOverlay:text-x:
412 *
413 * Resulting X position of font rendering.
414 */
415 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_X,
416 g_param_spec_int ("text-x", "horizontal position.",
417 "Resulting X position of font rendering.", -G_MAXINT,
418 G_MAXINT, DEFAULT_PROP_TEXT_X, G_PARAM_READABLE));
419
420 /**
421 * GstBaseTextOverlay:text-y:
422 *
423 * Resulting Y position of font rendering.
424 */
425 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_Y,
426 g_param_spec_int ("text-y", "vertical position",
427 "Resulting X position of font rendering.", -G_MAXINT,
428 G_MAXINT, DEFAULT_PROP_TEXT_Y, G_PARAM_READABLE));
429
430 /**
431 * GstBaseTextOverlay:text-width:
432 *
433 * Resulting width of font rendering.
434 */
435 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_WIDTH,
436 g_param_spec_uint ("text-width", "width",
437 "Resulting width of font rendering",
438 0, G_MAXINT, DEFAULT_PROP_TEXT_WIDTH, G_PARAM_READABLE));
439
440 /**
441 * GstBaseTextOverlay:text-height:
442 *
443 * Resulting height of font rendering.
444 */
445 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_HEIGHT,
446 g_param_spec_uint ("text-height", "height",
447 "Resulting height of font rendering", 0,
448 G_MAXINT, DEFAULT_PROP_TEXT_HEIGHT, G_PARAM_READABLE));
449
450 /**
451 * GstBaseTextOverlay:xpos:
452 *
453 * Horizontal position of the rendered text when using positioned alignment.
454 */
455 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
456 g_param_spec_double ("xpos", "horizontal position",
457 "Horizontal position when using clamped position alignment", 0, 1.0,
458 DEFAULT_PROP_XPOS,
459 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
460 /**
461 * GstBaseTextOverlay:ypos:
462 *
463 * Vertical position of the rendered text when using positioned alignment.
464 */
465 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
466 g_param_spec_double ("ypos", "vertical position",
467 "Vertical position when using clamped position alignment", 0, 1.0,
468 DEFAULT_PROP_YPOS,
469 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
470
471 /**
472 * GstBaseTextOverlay:x-absolute:
473 *
474 * Horizontal position of the rendered text when using absolute alignment.
475 *
476 * Maps the text area to be exactly inside of video canvas for [0, 0] - [1, 1]:
477 *
478 * [0, 0]: Top-Lefts of video and text are aligned
479 * [0.5, 0.5]: Centers are aligned
480 * [1, 1]: Bottom-Rights are aligned
481 *
482 * Values beyond [0, 0] - [1, 1] place the text outside of the video canvas.
483 *
484 * Since: 1.8
485 */
486 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X_ABSOLUTE,
487 g_param_spec_double ("x-absolute", "horizontal position",
488 "Horizontal position when using absolute alignment", -G_MAXDOUBLE,
489 G_MAXDOUBLE, DEFAULT_PROP_XPOS,
490 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
491 /**
492 * GstBaseTextOverlay:y-absolute:
493 *
494 * See x-absolute.
495 *
496 * Vertical position of the rendered text when using absolute alignment.
497 *
498 * Since: 1.8
499 */
500 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y_ABSOLUTE,
501 g_param_spec_double ("y-absolute", "vertical position",
502 "Vertical position when using absolute alignment", -G_MAXDOUBLE,
503 G_MAXDOUBLE, DEFAULT_PROP_YPOS,
504 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
505
506 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
507 g_param_spec_enum ("wrap-mode", "wrap mode",
508 "Whether to wrap the text and if so how.",
509 GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
510 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
511 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
512 g_param_spec_string ("font-desc", "font description",
513 "Pango font description of font to be used for rendering. "
514 "See documentation of pango_font_description_from_string "
515 "for syntax.", DEFAULT_PROP_FONT_DESC,
516 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
517 /**
518 * GstBaseTextOverlay:color:
519 *
520 * Color of the rendered text.
521 */
522 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
523 g_param_spec_uint ("color", "Color",
524 "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
525 DEFAULT_PROP_COLOR,
526 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
527 /**
528 * GstTextOverlay:outline-color:
529 *
530 * Color of the outline of the rendered text.
531 */
532 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
533 g_param_spec_uint ("outline-color", "Text Outline Color",
534 "Color to use for outline the text (big-endian ARGB).", 0,
535 G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
536 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
537
538 /**
539 * GstBaseTextOverlay:line-alignment:
540 *
541 * Alignment of text lines relative to each other (for multi-line text)
542 */
543 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
544 g_param_spec_enum ("line-alignment", "line alignment",
545 "Alignment of text lines relative to each other.",
546 GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
547 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
548 /**
549 * GstBaseTextOverlay:silent:
550 *
551 * If set, no text is rendered. Useful to switch off text rendering
552 * temporarily without removing the textoverlay element from the pipeline.
553 */
554 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
555 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
556 g_param_spec_boolean ("silent", "silent",
557 "Whether to render the text string",
558 DEFAULT_PROP_SILENT,
559 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
560 /**
561 * GstBaseTextOverlay:draw-shadow:
562 *
563 * If set, a text shadow is drawn.
564 *
565 * Since: 1.6
566 */
567 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_SHADOW,
568 g_param_spec_boolean ("draw-shadow", "draw-shadow",
569 "Whether to draw shadow",
570 DEFAULT_PROP_DRAW_SHADOW,
571 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
572 /**
573 * GstBaseTextOverlay:draw-outline:
574 *
575 * If set, an outline is drawn.
576 *
577 * Since: 1.6
578 */
579 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_OUTLINE,
580 g_param_spec_boolean ("draw-outline", "draw-outline",
581 "Whether to draw outline",
582 DEFAULT_PROP_DRAW_OUTLINE,
583 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
584 /**
585 * GstBaseTextOverlay:wait-text:
586 *
587 * If set, the video will block until a subtitle is received on the text pad.
588 * If video and subtitles are sent in sync, like from the same demuxer, this
589 * property should be set.
590 */
591 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
592 g_param_spec_boolean ("wait-text", "Wait Text",
593 "Whether to wait for subtitles",
594 DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
595
596 g_object_class_install_property (G_OBJECT_CLASS (klass),
597 PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
598 "Automatically adjust font size to screen-size.",
599 DEFAULT_PROP_AUTO_ADJUST_SIZE,
600 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
601
602 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
603 g_param_spec_boolean ("vertical-render", "vertical render",
604 "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
605 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
606
607 /**
608 * GstBaseTextOverlay:scale-mode:
609 *
610 * Scale text to compensate for and avoid distortion by subsequent video scaling
611 *
612 * Since: 1.14
613 */
614 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SCALE_MODE,
615 g_param_spec_enum ("scale-mode", "scale mode",
616 "Scale text to compensate for and avoid distortion by subsequent video scaling.",
617 GST_TYPE_BASE_TEXT_OVERLAY_SCALE_MODE, DEFAULT_PROP_SCALE_MODE,
618 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
619
620 /**
621 * GstBaseTextOverlay:scale-pixel-aspect-ratio:
622 *
623 * Video scaling pixel-aspect-ratio to compensate for in user scale-mode.
624 *
625 * Since: 1.14
626 */
627 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SCALE_PAR,
628 gst_param_spec_fraction ("scale-pixel-aspect-ratio",
629 "scale pixel aspect ratio",
630 "Pixel aspect ratio of video scale to compensate for in user scale-mode",
631 1, 100, 100, 1, DEFAULT_PROP_SCALE_PAR_N, DEFAULT_PROP_SCALE_PAR_D,
632 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
633 }
634
635 static void
gst_base_text_overlay_finalize(GObject * object)636 gst_base_text_overlay_finalize (GObject * object)
637 {
638 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
639
640 g_free (overlay->default_text);
641
642 if (overlay->composition) {
643 gst_video_overlay_composition_unref (overlay->composition);
644 overlay->composition = NULL;
645 }
646
647 if (overlay->text_image) {
648 gst_buffer_unref (overlay->text_image);
649 overlay->text_image = NULL;
650 }
651
652 if (overlay->layout) {
653 g_object_unref (overlay->layout);
654 overlay->layout = NULL;
655 }
656
657 if (overlay->text_buffer) {
658 gst_buffer_unref (overlay->text_buffer);
659 overlay->text_buffer = NULL;
660 }
661
662 if (overlay->pango_context) {
663 g_object_unref (overlay->pango_context);
664 overlay->pango_context = NULL;
665 }
666
667 g_mutex_clear (&overlay->lock);
668 g_cond_clear (&overlay->cond);
669
670 G_OBJECT_CLASS (parent_class)->finalize (object);
671 }
672
673 static void
gst_base_text_overlay_init(GstBaseTextOverlay * overlay,GstBaseTextOverlayClass * klass)674 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
675 GstBaseTextOverlayClass * klass)
676 {
677 GstPadTemplate *template;
678 PangoFontDescription *desc;
679 PangoFontMap *fontmap;
680
681 fontmap = pango_cairo_font_map_new ();
682 overlay->pango_context =
683 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
684 g_object_unref (fontmap);
685 pango_context_set_base_gravity (overlay->pango_context, PANGO_GRAVITY_SOUTH);
686
687 /* video sink */
688 template = gst_static_pad_template_get (&video_sink_template_factory);
689 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
690 gst_object_unref (template);
691 gst_pad_set_event_function (overlay->video_sinkpad,
692 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
693 gst_pad_set_chain_function (overlay->video_sinkpad,
694 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
695 gst_pad_set_query_function (overlay->video_sinkpad,
696 GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
697 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
698 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
699
700 template =
701 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
702 "text_sink");
703 if (template) {
704 /* text sink */
705 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
706
707 gst_pad_set_event_function (overlay->text_sinkpad,
708 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
709 gst_pad_set_chain_function (overlay->text_sinkpad,
710 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
711 gst_pad_set_link_function (overlay->text_sinkpad,
712 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
713 gst_pad_set_unlink_function (overlay->text_sinkpad,
714 GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
715 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
716 }
717
718 /* (video) source */
719 template = gst_static_pad_template_get (&src_template_factory);
720 overlay->srcpad = gst_pad_new_from_template (template, "src");
721 gst_object_unref (template);
722 gst_pad_set_event_function (overlay->srcpad,
723 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
724 gst_pad_set_query_function (overlay->srcpad,
725 GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
726 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
727
728 overlay->layout = pango_layout_new (overlay->pango_context);
729 desc = pango_context_get_font_description (overlay->pango_context);
730 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
731
732 overlay->color = DEFAULT_PROP_COLOR;
733 overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
734 overlay->halign = DEFAULT_PROP_HALIGNMENT;
735 overlay->valign = DEFAULT_PROP_VALIGNMENT;
736 overlay->xpad = DEFAULT_PROP_XPAD;
737 overlay->ypad = DEFAULT_PROP_YPAD;
738 overlay->deltax = DEFAULT_PROP_DELTAX;
739 overlay->deltay = DEFAULT_PROP_DELTAY;
740 overlay->xpos = DEFAULT_PROP_XPOS;
741 overlay->ypos = DEFAULT_PROP_YPOS;
742
743 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
744
745 overlay->want_shading = DEFAULT_PROP_SHADING;
746 overlay->shading_value = DEFAULT_PROP_SHADING_VALUE;
747 overlay->silent = DEFAULT_PROP_SILENT;
748 overlay->draw_shadow = DEFAULT_PROP_DRAW_SHADOW;
749 overlay->draw_outline = DEFAULT_PROP_DRAW_OUTLINE;
750 overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
751 overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
752
753 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
754 overlay->need_render = TRUE;
755 overlay->text_image = NULL;
756 overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
757 overlay->scale_mode = DEFAULT_PROP_SCALE_MODE;
758 overlay->scale_par_n = DEFAULT_PROP_SCALE_PAR_N;
759 overlay->scale_par_d = DEFAULT_PROP_SCALE_PAR_D;
760
761 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
762 pango_layout_set_alignment (overlay->layout,
763 (PangoAlignment) overlay->line_align);
764
765 overlay->text_buffer = NULL;
766 overlay->text_linked = FALSE;
767
768 overlay->composition = NULL;
769 overlay->upstream_composition = NULL;
770
771 overlay->width = 1;
772 overlay->height = 1;
773
774 overlay->window_width = 1;
775 overlay->window_height = 1;
776
777 overlay->text_width = DEFAULT_PROP_TEXT_WIDTH;
778 overlay->text_height = DEFAULT_PROP_TEXT_HEIGHT;
779
780 overlay->text_x = DEFAULT_PROP_TEXT_X;
781 overlay->text_y = DEFAULT_PROP_TEXT_Y;
782
783 overlay->render_width = 1;
784 overlay->render_height = 1;
785 overlay->render_scale = 1.0l;
786
787 g_mutex_init (&overlay->lock);
788 g_cond_init (&overlay->cond);
789 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
790 }
791
792 static void
gst_base_text_overlay_set_wrap_mode(GstBaseTextOverlay * overlay,gint width)793 gst_base_text_overlay_set_wrap_mode (GstBaseTextOverlay * overlay, gint width)
794 {
795 if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
796 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
797 pango_layout_set_width (overlay->layout, -1);
798 } else {
799 width = width * PANGO_SCALE;
800
801 GST_DEBUG_OBJECT (overlay, "Set layout width %d", width);
802 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
803 pango_layout_set_width (overlay->layout, width);
804 }
805
806 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
807 }
808
809 static gboolean
gst_base_text_overlay_setcaps_txt(GstBaseTextOverlay * overlay,GstCaps * caps)810 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
811 {
812 GstStructure *structure;
813 const gchar *format;
814
815 structure = gst_caps_get_structure (caps, 0);
816 format = gst_structure_get_string (structure, "format");
817 overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
818
819 return TRUE;
820 }
821
822 /* only negotiate/query video overlay composition support for now */
823 static gboolean
gst_base_text_overlay_negotiate(GstBaseTextOverlay * overlay,GstCaps * caps)824 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay, GstCaps * caps)
825 {
826 gboolean upstream_has_meta = FALSE;
827 gboolean caps_has_meta = FALSE;
828 gboolean alloc_has_meta = FALSE;
829 gboolean attach = FALSE;
830 gboolean ret = TRUE;
831 guint width, height;
832 GstCapsFeatures *f;
833 GstCaps *overlay_caps;
834 GstQuery *query;
835 guint alloc_index;
836
837 GST_DEBUG_OBJECT (overlay, "performing negotiation");
838
839 /* Clear any pending reconfigure to avoid negotiating twice */
840 gst_pad_check_reconfigure (overlay->srcpad);
841
842 if (!caps)
843 caps = gst_pad_get_current_caps (overlay->video_sinkpad);
844 else
845 gst_caps_ref (caps);
846
847 if (!caps || gst_caps_is_empty (caps))
848 goto no_format;
849
850 /* Check if upstream caps have meta */
851 if ((f = gst_caps_get_features (caps, 0))) {
852 upstream_has_meta = gst_caps_features_contains (f,
853 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
854 }
855
856 /* Initialize dimensions */
857 width = overlay->width;
858 height = overlay->height;
859
860 if (upstream_has_meta) {
861 overlay_caps = gst_caps_ref (caps);
862 } else {
863 GstCaps *peercaps;
864
865 /* BaseTransform requires caps for the allocation query to work */
866 overlay_caps = gst_caps_copy (caps);
867 f = gst_caps_get_features (overlay_caps, 0);
868 gst_caps_features_add (f,
869 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
870
871 /* Then check if downstream accept overlay composition in caps */
872 /* FIXME: We should probably check if downstream *prefers* the
873 * overlay meta, and only enforce usage of it if we can't handle
874 * the format ourselves and thus would have to drop the overlays.
875 * Otherwise we should prefer what downstream wants here.
876 */
877 peercaps = gst_pad_peer_query_caps (overlay->srcpad, overlay_caps);
878 caps_has_meta = !gst_caps_is_empty (peercaps);
879 gst_caps_unref (peercaps);
880
881 GST_DEBUG_OBJECT (overlay, "caps have overlay meta %d", caps_has_meta);
882 }
883
884 if (upstream_has_meta || caps_has_meta) {
885 /* Send caps immediatly, it's needed by GstBaseTransform to get a reply
886 * from allocation query */
887 ret = gst_pad_set_caps (overlay->srcpad, overlay_caps);
888
889 /* First check if the allocation meta has compositon */
890 query = gst_query_new_allocation (overlay_caps, FALSE);
891
892 if (!gst_pad_peer_query (overlay->srcpad, query)) {
893 /* no problem, we use the query defaults */
894 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
895
896 /* In case we were flushing, mark reconfigure and fail this method,
897 * will make it retry */
898 if (overlay->video_flushing)
899 ret = FALSE;
900 }
901
902 alloc_has_meta = gst_query_find_allocation_meta (query,
903 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, &alloc_index);
904
905 GST_DEBUG_OBJECT (overlay, "sink alloc has overlay meta %d",
906 alloc_has_meta);
907
908 if (alloc_has_meta) {
909 const GstStructure *params;
910
911 gst_query_parse_nth_allocation_meta (query, alloc_index, ¶ms);
912 if (params) {
913 if (gst_structure_get (params, "width", G_TYPE_UINT, &width,
914 "height", G_TYPE_UINT, &height, NULL)) {
915 GST_DEBUG_OBJECT (overlay, "received window size: %dx%d", width,
916 height);
917 g_assert (width != 0 && height != 0);
918 }
919 }
920 }
921
922 gst_query_unref (query);
923 }
924
925 /* Update render size if needed */
926 overlay->window_width = width;
927 overlay->window_height = height;
928 gst_base_text_overlay_update_render_size (overlay);
929
930 /* For backward compatibility, we will prefer blitting if downstream
931 * allocation does not support the meta. In other case we will prefer
932 * attaching, and will fail the negotiation in the unlikely case we are
933 * force to blit, but format isn't supported. */
934
935 if (upstream_has_meta) {
936 attach = TRUE;
937 } else if (caps_has_meta) {
938 if (alloc_has_meta) {
939 attach = TRUE;
940 } else {
941 /* Don't attach unless we cannot handle the format */
942 attach = !gst_base_text_overlay_can_handle_caps (caps);
943 }
944 } else {
945 ret = gst_base_text_overlay_can_handle_caps (caps);
946 }
947
948 /* If we attach, then pick the overlay caps */
949 if (attach) {
950 GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, overlay_caps);
951 /* Caps where already sent */
952 } else if (ret) {
953 GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps);
954 ret = gst_pad_set_caps (overlay->srcpad, caps);
955 }
956
957 overlay->attach_compo_to_buffer = attach;
958
959 if (!ret) {
960 GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure");
961 gst_pad_mark_reconfigure (overlay->srcpad);
962 }
963
964 gst_caps_unref (overlay_caps);
965 gst_caps_unref (caps);
966
967 return ret;
968
969 no_format:
970 {
971 if (caps)
972 gst_caps_unref (caps);
973 gst_pad_mark_reconfigure (overlay->srcpad);
974 return FALSE;
975 }
976 }
977
978 static gboolean
gst_base_text_overlay_can_handle_caps(GstCaps * incaps)979 gst_base_text_overlay_can_handle_caps (GstCaps * incaps)
980 {
981 gboolean ret;
982 GstCaps *caps;
983
984 caps = gst_static_caps_get (&sw_template_caps);
985 ret = gst_caps_is_subset (incaps, caps);
986 gst_caps_unref (caps);
987
988 return ret;
989 }
990
991 static gboolean
gst_base_text_overlay_setcaps(GstBaseTextOverlay * overlay,GstCaps * caps)992 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
993 {
994 GstVideoInfo info;
995 gboolean ret = FALSE;
996
997 if (!gst_video_info_from_caps (&info, caps))
998 goto invalid_caps;
999
1000 /* Render again if size have changed */
1001 if (GST_VIDEO_INFO_WIDTH (&info) != GST_VIDEO_INFO_WIDTH (&overlay->info) ||
1002 GST_VIDEO_INFO_HEIGHT (&info) != GST_VIDEO_INFO_HEIGHT (&overlay->info))
1003 overlay->need_render = TRUE;
1004
1005 overlay->info = info;
1006 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
1007 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
1008 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
1009
1010 ret = gst_base_text_overlay_negotiate (overlay, caps);
1011
1012 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1013
1014 if (!overlay->attach_compo_to_buffer &&
1015 !gst_base_text_overlay_can_handle_caps (caps)) {
1016 GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps);
1017 ret = FALSE;
1018 }
1019 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1020
1021 return ret;
1022
1023 /* ERRORS */
1024 invalid_caps:
1025 {
1026 GST_DEBUG_OBJECT (overlay, "could not parse caps");
1027 return FALSE;
1028 }
1029 }
1030
1031 static void
gst_base_text_overlay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1032 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
1033 const GValue * value, GParamSpec * pspec)
1034 {
1035 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
1036
1037 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1038 switch (prop_id) {
1039 case PROP_TEXT:
1040 g_free (overlay->default_text);
1041 overlay->default_text = g_value_dup_string (value);
1042 break;
1043 case PROP_SHADING:
1044 overlay->want_shading = g_value_get_boolean (value);
1045 break;
1046 case PROP_XPAD:
1047 overlay->xpad = g_value_get_int (value);
1048 break;
1049 case PROP_YPAD:
1050 overlay->ypad = g_value_get_int (value);
1051 break;
1052 case PROP_DELTAX:
1053 overlay->deltax = g_value_get_int (value);
1054 break;
1055 case PROP_DELTAY:
1056 overlay->deltay = g_value_get_int (value);
1057 break;
1058 case PROP_XPOS:
1059 overlay->xpos = g_value_get_double (value);
1060 break;
1061 case PROP_YPOS:
1062 overlay->ypos = g_value_get_double (value);
1063 break;
1064 case PROP_X_ABSOLUTE:
1065 overlay->xpos = g_value_get_double (value);
1066 break;
1067 case PROP_Y_ABSOLUTE:
1068 overlay->ypos = g_value_get_double (value);
1069 break;
1070 case PROP_VALIGNMENT:
1071 overlay->valign = g_value_get_enum (value);
1072 break;
1073 case PROP_HALIGNMENT:
1074 overlay->halign = g_value_get_enum (value);
1075 break;
1076 case PROP_WRAP_MODE:
1077 overlay->wrap_mode = g_value_get_enum (value);
1078 break;
1079 case PROP_FONT_DESC:
1080 {
1081 PangoFontDescription *desc;
1082 const gchar *fontdesc_str;
1083
1084 fontdesc_str = g_value_get_string (value);
1085 desc = pango_font_description_from_string (fontdesc_str);
1086 if (desc) {
1087 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
1088 pango_layout_set_font_description (overlay->layout, desc);
1089 gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
1090 pango_font_description_free (desc);
1091 } else {
1092 GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
1093 fontdesc_str);
1094 }
1095 break;
1096 }
1097 case PROP_COLOR:
1098 overlay->color = g_value_get_uint (value);
1099 break;
1100 case PROP_OUTLINE_COLOR:
1101 overlay->outline_color = g_value_get_uint (value);
1102 break;
1103 case PROP_SILENT:
1104 overlay->silent = g_value_get_boolean (value);
1105 break;
1106 case PROP_DRAW_SHADOW:
1107 overlay->draw_shadow = g_value_get_boolean (value);
1108 break;
1109 case PROP_DRAW_OUTLINE:
1110 overlay->draw_outline = g_value_get_boolean (value);
1111 break;
1112 case PROP_LINE_ALIGNMENT:
1113 overlay->line_align = g_value_get_enum (value);
1114 pango_layout_set_alignment (overlay->layout,
1115 (PangoAlignment) overlay->line_align);
1116 break;
1117 case PROP_WAIT_TEXT:
1118 overlay->wait_text = g_value_get_boolean (value);
1119 break;
1120 case PROP_AUTO_ADJUST_SIZE:
1121 overlay->auto_adjust_size = g_value_get_boolean (value);
1122 break;
1123 case PROP_VERTICAL_RENDER:
1124 overlay->use_vertical_render = g_value_get_boolean (value);
1125 if (overlay->use_vertical_render) {
1126 overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1127 overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1128 overlay->line_align = GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT;
1129 pango_layout_set_alignment (overlay->layout,
1130 (PangoAlignment) overlay->line_align);
1131 }
1132 break;
1133 case PROP_SCALE_MODE:
1134 overlay->scale_mode = g_value_get_enum (value);
1135 break;
1136 case PROP_SCALE_PAR:
1137 overlay->scale_par_n = gst_value_get_fraction_numerator (value);
1138 overlay->scale_par_d = gst_value_get_fraction_denominator (value);
1139 break;
1140 case PROP_SHADING_VALUE:
1141 overlay->shading_value = g_value_get_uint (value);
1142 break;
1143 default:
1144 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1145 break;
1146 }
1147
1148 overlay->need_render = TRUE;
1149 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1150 }
1151
1152 static void
gst_base_text_overlay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1153 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
1154 GValue * value, GParamSpec * pspec)
1155 {
1156 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
1157
1158 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1159 switch (prop_id) {
1160 case PROP_TEXT:
1161 g_value_set_string (value, overlay->default_text);
1162 break;
1163 case PROP_SHADING:
1164 g_value_set_boolean (value, overlay->want_shading);
1165 break;
1166 case PROP_XPAD:
1167 g_value_set_int (value, overlay->xpad);
1168 break;
1169 case PROP_YPAD:
1170 g_value_set_int (value, overlay->ypad);
1171 break;
1172 case PROP_DELTAX:
1173 g_value_set_int (value, overlay->deltax);
1174 break;
1175 case PROP_DELTAY:
1176 g_value_set_int (value, overlay->deltay);
1177 break;
1178 case PROP_XPOS:
1179 g_value_set_double (value, overlay->xpos);
1180 break;
1181 case PROP_YPOS:
1182 g_value_set_double (value, overlay->ypos);
1183 break;
1184 case PROP_X_ABSOLUTE:
1185 g_value_set_double (value, overlay->xpos);
1186 break;
1187 case PROP_Y_ABSOLUTE:
1188 g_value_set_double (value, overlay->ypos);
1189 break;
1190 case PROP_VALIGNMENT:
1191 g_value_set_enum (value, overlay->valign);
1192 break;
1193 case PROP_HALIGNMENT:
1194 g_value_set_enum (value, overlay->halign);
1195 break;
1196 case PROP_WRAP_MODE:
1197 g_value_set_enum (value, overlay->wrap_mode);
1198 break;
1199 case PROP_SILENT:
1200 g_value_set_boolean (value, overlay->silent);
1201 break;
1202 case PROP_DRAW_SHADOW:
1203 g_value_set_boolean (value, overlay->draw_shadow);
1204 break;
1205 case PROP_DRAW_OUTLINE:
1206 g_value_set_boolean (value, overlay->draw_outline);
1207 break;
1208 case PROP_LINE_ALIGNMENT:
1209 g_value_set_enum (value, overlay->line_align);
1210 break;
1211 case PROP_WAIT_TEXT:
1212 g_value_set_boolean (value, overlay->wait_text);
1213 break;
1214 case PROP_AUTO_ADJUST_SIZE:
1215 g_value_set_boolean (value, overlay->auto_adjust_size);
1216 break;
1217 case PROP_VERTICAL_RENDER:
1218 g_value_set_boolean (value, overlay->use_vertical_render);
1219 break;
1220 case PROP_SCALE_MODE:
1221 g_value_set_enum (value, overlay->scale_mode);
1222 break;
1223 case PROP_SCALE_PAR:
1224 gst_value_set_fraction (value, overlay->scale_par_n,
1225 overlay->scale_par_d);
1226 break;
1227 case PROP_COLOR:
1228 g_value_set_uint (value, overlay->color);
1229 break;
1230 case PROP_OUTLINE_COLOR:
1231 g_value_set_uint (value, overlay->outline_color);
1232 break;
1233 case PROP_SHADING_VALUE:
1234 g_value_set_uint (value, overlay->shading_value);
1235 break;
1236 case PROP_FONT_DESC:
1237 {
1238 const PangoFontDescription *desc;
1239
1240 desc = pango_layout_get_font_description (overlay->layout);
1241 if (!desc)
1242 g_value_set_string (value, "");
1243 else {
1244 g_value_take_string (value, pango_font_description_to_string (desc));
1245 }
1246 break;
1247 }
1248 case PROP_TEXT_X:
1249 g_value_set_int (value, overlay->text_x);
1250 break;
1251 case PROP_TEXT_Y:
1252 g_value_set_int (value, overlay->text_y);
1253 break;
1254 case PROP_TEXT_WIDTH:
1255 g_value_set_uint (value, overlay->text_width);
1256 break;
1257 case PROP_TEXT_HEIGHT:
1258 g_value_set_uint (value, overlay->text_height);
1259 break;
1260 default:
1261 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1262 break;
1263 }
1264
1265 overlay->need_render = TRUE;
1266 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1267 }
1268
1269 static gboolean
gst_base_text_overlay_src_query(GstPad * pad,GstObject * parent,GstQuery * query)1270 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1271 GstQuery * query)
1272 {
1273 gboolean ret = FALSE;
1274 GstBaseTextOverlay *overlay;
1275
1276 overlay = GST_BASE_TEXT_OVERLAY (parent);
1277
1278 switch (GST_QUERY_TYPE (query)) {
1279 case GST_QUERY_CAPS:
1280 {
1281 GstCaps *filter, *caps;
1282
1283 gst_query_parse_caps (query, &filter);
1284 caps = gst_base_text_overlay_get_src_caps (pad, overlay, filter);
1285 gst_query_set_caps_result (query, caps);
1286 gst_caps_unref (caps);
1287 ret = TRUE;
1288 break;
1289 }
1290 default:
1291 ret = gst_pad_query_default (pad, parent, query);
1292 break;
1293 }
1294
1295 return ret;
1296 }
1297
1298 static void
gst_base_text_overlay_update_render_size(GstBaseTextOverlay * overlay)1299 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay)
1300 {
1301 gdouble video_aspect = (gdouble) overlay->width / (gdouble) overlay->height;
1302 gdouble window_aspect = (gdouble) overlay->window_width /
1303 (gdouble) overlay->window_height;
1304
1305 guint text_buffer_width = 0;
1306 guint text_buffer_height = 0;
1307
1308 if (video_aspect >= window_aspect) {
1309 text_buffer_width = overlay->window_width;
1310 text_buffer_height = window_aspect * overlay->window_height / video_aspect;
1311 } else if (video_aspect < window_aspect) {
1312 text_buffer_width = video_aspect * overlay->window_width / window_aspect;
1313 text_buffer_height = overlay->window_height;
1314 }
1315
1316 if ((overlay->render_width == text_buffer_width) &&
1317 (overlay->render_height == text_buffer_height))
1318 return;
1319
1320 overlay->need_render = TRUE;
1321 overlay->render_width = text_buffer_width;
1322 overlay->render_height = text_buffer_height;
1323 overlay->render_scale = (gdouble) overlay->render_width /
1324 (gdouble) overlay->width;
1325
1326 GST_DEBUG ("updating render dimensions %dx%d from stream %dx%d, window %dx%d "
1327 "and render scale %f", overlay->render_width, overlay->render_height,
1328 overlay->width, overlay->height, overlay->window_width,
1329 overlay->window_height, overlay->render_scale);
1330 }
1331
1332 static gboolean
gst_base_text_overlay_src_event(GstPad * pad,GstObject * parent,GstEvent * event)1333 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1334 GstEvent * event)
1335 {
1336 GstBaseTextOverlay *overlay;
1337 gboolean ret;
1338
1339 overlay = GST_BASE_TEXT_OVERLAY (parent);
1340
1341 if (overlay->text_linked) {
1342 gst_event_ref (event);
1343 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1344 gst_pad_push_event (overlay->text_sinkpad, event);
1345 } else {
1346 ret = gst_pad_push_event (overlay->video_sinkpad, event);
1347 }
1348
1349 return ret;
1350 }
1351
1352 /* gst_base_text_overlay_add_feature_and_intersect:
1353 *
1354 * Creates a new #GstCaps containing the (given caps +
1355 * given caps feature) + (given caps intersected by the
1356 * given filter).
1357 *
1358 * Returns: the new #GstCaps
1359 */
1360 static GstCaps *
gst_base_text_overlay_add_feature_and_intersect(GstCaps * caps,const gchar * feature,GstCaps * filter)1361 gst_base_text_overlay_add_feature_and_intersect (GstCaps * caps,
1362 const gchar * feature, GstCaps * filter)
1363 {
1364 int i, caps_size;
1365 GstCaps *new_caps;
1366
1367 new_caps = gst_caps_copy (caps);
1368
1369 caps_size = gst_caps_get_size (new_caps);
1370 for (i = 0; i < caps_size; i++) {
1371 GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
1372
1373 if (!gst_caps_features_is_any (features)) {
1374 gst_caps_features_add (features, feature);
1375 }
1376 }
1377
1378 gst_caps_append (new_caps, gst_caps_intersect_full (caps,
1379 filter, GST_CAPS_INTERSECT_FIRST));
1380
1381 return new_caps;
1382 }
1383
1384 /* gst_base_text_overlay_intersect_by_feature:
1385 *
1386 * Creates a new #GstCaps based on the following filtering rule.
1387 *
1388 * For each individual caps contained in given caps, if the
1389 * caps uses the given caps feature, keep a version of the caps
1390 * with the feature and an another one without. Otherwise, intersect
1391 * the caps with the given filter.
1392 *
1393 * Returns: the new #GstCaps
1394 */
1395 static GstCaps *
gst_base_text_overlay_intersect_by_feature(GstCaps * caps,const gchar * feature,GstCaps * filter)1396 gst_base_text_overlay_intersect_by_feature (GstCaps * caps,
1397 const gchar * feature, GstCaps * filter)
1398 {
1399 int i, caps_size;
1400 GstCaps *new_caps;
1401
1402 new_caps = gst_caps_new_empty ();
1403
1404 caps_size = gst_caps_get_size (caps);
1405 for (i = 0; i < caps_size; i++) {
1406 GstStructure *caps_structure = gst_caps_get_structure (caps, i);
1407 GstCapsFeatures *caps_features =
1408 gst_caps_features_copy (gst_caps_get_features (caps, i));
1409 GstCaps *filtered_caps;
1410 GstCaps *simple_caps =
1411 gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
1412 gst_caps_set_features (simple_caps, 0, caps_features);
1413
1414 if (gst_caps_features_contains (caps_features, feature)) {
1415 gst_caps_append (new_caps, gst_caps_copy (simple_caps));
1416
1417 gst_caps_features_remove (caps_features, feature);
1418 filtered_caps = gst_caps_ref (simple_caps);
1419 } else {
1420 filtered_caps = gst_caps_intersect_full (simple_caps, filter,
1421 GST_CAPS_INTERSECT_FIRST);
1422 }
1423
1424 gst_caps_unref (simple_caps);
1425 gst_caps_append (new_caps, filtered_caps);
1426 }
1427
1428 return new_caps;
1429 }
1430
1431 static GstCaps *
gst_base_text_overlay_get_videosink_caps(GstPad * pad,GstBaseTextOverlay * overlay,GstCaps * filter)1432 gst_base_text_overlay_get_videosink_caps (GstPad * pad,
1433 GstBaseTextOverlay * overlay, GstCaps * filter)
1434 {
1435 GstPad *srcpad = overlay->srcpad;
1436 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1437
1438 if (filter) {
1439 /* filter caps + composition feature + filter caps
1440 * filtered by the software caps. */
1441 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1442 overlay_filter = gst_base_text_overlay_add_feature_and_intersect (filter,
1443 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1444 gst_caps_unref (sw_caps);
1445
1446 GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
1447 overlay_filter);
1448 }
1449
1450 peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
1451
1452 if (overlay_filter)
1453 gst_caps_unref (overlay_filter);
1454
1455 if (peer_caps) {
1456
1457 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
1458
1459 if (gst_caps_is_any (peer_caps)) {
1460 /* if peer returns ANY caps, return filtered src pad template caps */
1461 caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
1462 } else {
1463
1464 /* duplicate caps which contains the composition into one version with
1465 * the meta and one without. Filter the other caps by the software caps */
1466 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1467 caps = gst_base_text_overlay_intersect_by_feature (peer_caps,
1468 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1469 gst_caps_unref (sw_caps);
1470 }
1471
1472 gst_caps_unref (peer_caps);
1473
1474 } else {
1475 /* no peer, our padtemplate is enough then */
1476 caps = gst_pad_get_pad_template_caps (pad);
1477 }
1478
1479 if (filter) {
1480 GstCaps *intersection = gst_caps_intersect_full (filter, caps,
1481 GST_CAPS_INTERSECT_FIRST);
1482 gst_caps_unref (caps);
1483 caps = intersection;
1484 }
1485
1486 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1487
1488 return caps;
1489 }
1490
1491 static GstCaps *
gst_base_text_overlay_get_src_caps(GstPad * pad,GstBaseTextOverlay * overlay,GstCaps * filter)1492 gst_base_text_overlay_get_src_caps (GstPad * pad, GstBaseTextOverlay * overlay,
1493 GstCaps * filter)
1494 {
1495 GstPad *sinkpad = overlay->video_sinkpad;
1496 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1497
1498 if (filter) {
1499 /* duplicate filter caps which contains the composition into one version
1500 * with the meta and one without. Filter the other caps by the software
1501 * caps */
1502 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1503 overlay_filter =
1504 gst_base_text_overlay_intersect_by_feature (filter,
1505 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1506 gst_caps_unref (sw_caps);
1507 }
1508
1509 peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
1510
1511 if (overlay_filter)
1512 gst_caps_unref (overlay_filter);
1513
1514 if (peer_caps) {
1515
1516 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
1517
1518 if (gst_caps_is_any (peer_caps)) {
1519
1520 /* if peer returns ANY caps, return filtered sink pad template caps */
1521 caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
1522
1523 } else {
1524
1525 /* return upstream caps + composition feature + upstream caps
1526 * filtered by the software caps. */
1527 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1528 caps = gst_base_text_overlay_add_feature_and_intersect (peer_caps,
1529 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1530 gst_caps_unref (sw_caps);
1531 }
1532
1533 gst_caps_unref (peer_caps);
1534
1535 } else {
1536 /* no peer, our padtemplate is enough then */
1537 caps = gst_pad_get_pad_template_caps (pad);
1538 }
1539
1540 if (filter) {
1541 GstCaps *intersection;
1542
1543 intersection =
1544 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1545 gst_caps_unref (caps);
1546 caps = intersection;
1547 }
1548 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
1549
1550 return caps;
1551 }
1552
1553 static void
gst_base_text_overlay_adjust_values_with_fontdesc(GstBaseTextOverlay * overlay,PangoFontDescription * desc)1554 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1555 PangoFontDescription * desc)
1556 {
1557 gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1558 overlay->shadow_offset = (double) (font_size) / 13.0;
1559 overlay->outline_offset = (double) (font_size) / 15.0;
1560 if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1561 overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1562 }
1563
1564 static void
gst_base_text_overlay_get_pos(GstBaseTextOverlay * overlay,gint * xpos,gint * ypos)1565 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1566 gint * xpos, gint * ypos)
1567 {
1568 gint width, height;
1569
1570 width = overlay->logical_rect.width;
1571 height = overlay->logical_rect.height;
1572
1573 *xpos = overlay->ink_rect.x - overlay->logical_rect.x;
1574 switch (overlay->halign) {
1575 case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1576 *xpos += overlay->xpad;
1577 *xpos = MAX (0, *xpos);
1578 break;
1579 case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1580 *xpos += (overlay->width - width) / 2;
1581 break;
1582 case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1583 *xpos += overlay->width - width - overlay->xpad;
1584 *xpos = MIN (overlay->width - overlay->ink_rect.width, *xpos);
1585 break;
1586 case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1587 *xpos += (gint) (overlay->width * overlay->xpos) - width / 2;
1588 *xpos = CLAMP (*xpos, 0, overlay->width - overlay->ink_rect.width);
1589 if (*xpos < 0)
1590 *xpos = 0;
1591 break;
1592 case GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE:
1593 *xpos = (overlay->width - overlay->text_width) * overlay->xpos;
1594 break;
1595 default:
1596 *xpos = 0;
1597 }
1598 *xpos += overlay->deltax;
1599
1600 *ypos = overlay->ink_rect.y - overlay->logical_rect.y;
1601 switch (overlay->valign) {
1602 case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1603 /* This will be the same as baseline, if there is enough padding,
1604 * otherwise it will avoid clipping the text */
1605 *ypos += overlay->height - height - overlay->ypad;
1606 *ypos = MIN (overlay->height - overlay->ink_rect.height, *ypos);
1607 break;
1608 case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1609 *ypos += overlay->height - height - overlay->ypad;
1610 /* Don't clip, this would not respect the base line */
1611 break;
1612 case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1613 *ypos += overlay->ypad;
1614 *ypos = MAX (0.0, *ypos);
1615 break;
1616 case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1617 *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1618 *ypos = CLAMP (*ypos, 0, overlay->height - overlay->ink_rect.height);
1619 break;
1620 case GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE:
1621 *ypos = (overlay->height - overlay->text_height) * overlay->ypos;
1622 break;
1623 case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1624 *ypos = (overlay->height - height) / 2;
1625 break;
1626 default:
1627 *ypos = overlay->ypad;
1628 break;
1629 }
1630 *ypos += overlay->deltay;
1631
1632 overlay->text_x = *xpos;
1633 overlay->text_y = *ypos;
1634
1635 GST_DEBUG_OBJECT (overlay, "Placing overlay at (%d, %d)", *xpos, *ypos);
1636 }
1637
1638 static inline void
gst_base_text_overlay_set_composition(GstBaseTextOverlay * overlay)1639 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1640 {
1641 gint xpos, ypos;
1642 GstVideoOverlayRectangle *rectangle;
1643
1644 if (overlay->text_image && overlay->text_width != 1) {
1645 gint render_width, render_height;
1646
1647 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1648
1649 render_width = overlay->ink_rect.width;
1650 render_height = overlay->ink_rect.height;
1651
1652 GST_DEBUG ("updating composition for '%s' with window size %dx%d, "
1653 "buffer size %dx%d, render size %dx%d and position (%d, %d)",
1654 overlay->default_text, overlay->window_width, overlay->window_height,
1655 overlay->text_width, overlay->text_height, render_width,
1656 render_height, xpos, ypos);
1657
1658 gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1659 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1660 overlay->text_width, overlay->text_height);
1661
1662 rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1663 xpos, ypos, render_width, render_height,
1664 GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1665
1666 if (overlay->composition)
1667 gst_video_overlay_composition_unref (overlay->composition);
1668
1669 if (overlay->upstream_composition) {
1670 overlay->composition =
1671 gst_video_overlay_composition_copy (overlay->upstream_composition);
1672 gst_video_overlay_composition_add_rectangle (overlay->composition,
1673 rectangle);
1674 } else {
1675 overlay->composition = gst_video_overlay_composition_new (rectangle);
1676 }
1677
1678 gst_video_overlay_rectangle_unref (rectangle);
1679
1680 } else if (overlay->composition) {
1681 gst_video_overlay_composition_unref (overlay->composition);
1682 overlay->composition = NULL;
1683 }
1684 }
1685
1686 static gboolean
gst_text_overlay_filter_foreground_attr(PangoAttribute * attr,gpointer data)1687 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1688 {
1689 if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1690 return FALSE;
1691 } else {
1692 return TRUE;
1693 }
1694 }
1695
1696 static void
gst_base_text_overlay_render_pangocairo(GstBaseTextOverlay * overlay,const gchar * string,gint textlen)1697 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1698 const gchar * string, gint textlen)
1699 {
1700 cairo_t *cr;
1701 cairo_surface_t *surface;
1702 PangoRectangle ink_rect, logical_rect;
1703 cairo_matrix_t cairo_matrix;
1704 gint unscaled_width, unscaled_height;
1705 gint width, height;
1706 gboolean full_width = FALSE;
1707 double scalef_x = 1.0, scalef_y = 1.0;
1708 double a, r, g, b;
1709 gdouble shadow_offset = 0.0;
1710 gdouble outline_offset = 0.0;
1711 gint xpad = 0, ypad = 0;
1712 GstBuffer *buffer;
1713 GstMapInfo map;
1714
1715 if (overlay->auto_adjust_size) {
1716 /* 640 pixel is default */
1717 scalef_x = scalef_y = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1718 }
1719
1720 if (overlay->scale_mode != GST_BASE_TEXT_OVERLAY_SCALE_MODE_NONE) {
1721 gint par_n = 1, par_d = 1;
1722
1723 switch (overlay->scale_mode) {
1724 case GST_BASE_TEXT_OVERLAY_SCALE_MODE_PAR:
1725 par_n = overlay->info.par_n;
1726 par_d = overlay->info.par_d;
1727 break;
1728 case GST_BASE_TEXT_OVERLAY_SCALE_MODE_DISPLAY:
1729 /* (width * par_n) / (height * par_d) = (display_w / display_h) */
1730 if (!gst_util_fraction_multiply (overlay->window_width,
1731 overlay->window_height, overlay->height, overlay->width,
1732 &par_n, &par_d)) {
1733 GST_WARNING_OBJECT (overlay,
1734 "Can't figure out display ratio, defaulting to 1:1");
1735 par_n = par_d = 1;
1736 }
1737 break;
1738 case GST_BASE_TEXT_OVERLAY_SCALE_MODE_USER:
1739 par_n = overlay->scale_par_n;
1740 par_d = overlay->scale_par_d;
1741 break;
1742 default:
1743 break;
1744 }
1745 /* sanitize */
1746 if (!par_n || !par_d)
1747 par_n = par_d = 1;
1748 /* compensate later scaling as would be done for a par_n / par_d p-a-r;
1749 * apply all scaling to y so as to allow for predictable text width
1750 * layout independent of the presentation aspect scaling */
1751 if (overlay->use_vertical_render) {
1752 scalef_y *= ((gdouble) par_d) / ((gdouble) par_n);
1753 } else {
1754 scalef_y *= ((gdouble) par_n) / ((gdouble) par_d);
1755 }
1756 GST_DEBUG_OBJECT (overlay,
1757 "compensate scaling mode %d par %d/%d, scale %f, %f",
1758 overlay->scale_mode, par_n, par_d, scalef_x, scalef_y);
1759 }
1760
1761 if (overlay->draw_shadow)
1762 shadow_offset = ceil (overlay->shadow_offset);
1763
1764 /* This value is uses as cairo line width, which is the diameter of a pen
1765 * that is circular. That's why only half of it is used to offset */
1766 if (overlay->draw_outline)
1767 outline_offset = ceil (overlay->outline_offset);
1768
1769 if (overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_LEFT ||
1770 overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT)
1771 xpad = overlay->xpad;
1772
1773 if (overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_TOP ||
1774 overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM)
1775 ypad = overlay->ypad;
1776
1777 pango_layout_set_width (overlay->layout, -1);
1778 /* set text on pango layout */
1779 pango_layout_set_markup (overlay->layout, string, textlen);
1780
1781 /* get subtitle image size */
1782 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1783
1784 unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1785 width = ceil (unscaled_width * scalef_x);
1786
1787 /*
1788 * subtitle image width can be larger then overlay width
1789 * so rearrange overlay wrap mode.
1790 */
1791 if (overlay->use_vertical_render) {
1792 if (width + ypad > overlay->height) {
1793 width = overlay->height - ypad;
1794 full_width = TRUE;
1795 }
1796 } else if (width + xpad > overlay->width) {
1797 width = overlay->width - xpad;
1798 full_width = TRUE;
1799 }
1800
1801 if (full_width) {
1802 unscaled_width = width / scalef_x;
1803 gst_base_text_overlay_set_wrap_mode (overlay,
1804 unscaled_width - shadow_offset - outline_offset);
1805 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1806
1807 unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1808 width = ceil (unscaled_width * scalef_x);
1809 }
1810
1811 unscaled_height = ink_rect.height + shadow_offset + outline_offset;
1812 height = ceil (unscaled_height * scalef_y);
1813
1814 if (overlay->use_vertical_render) {
1815 if (height + xpad > overlay->width) {
1816 height = overlay->width - xpad;
1817 unscaled_height = width / scalef_y;
1818 }
1819 } else if (height + ypad > overlay->height) {
1820 height = overlay->height - ypad;
1821 unscaled_height = height / scalef_y;
1822 }
1823
1824 GST_DEBUG_OBJECT (overlay, "Rendering with ink rect (%d, %d) %dx%d and "
1825 "logical rect (%d, %d) %dx%d", ink_rect.x, ink_rect.y, ink_rect.width,
1826 ink_rect.height, logical_rect.x, logical_rect.y, logical_rect.width,
1827 logical_rect.height);
1828 GST_DEBUG_OBJECT (overlay, "Rendering with width %d and height %d "
1829 "(shadow %f, outline %f)", unscaled_width, unscaled_height,
1830 shadow_offset, outline_offset);
1831
1832
1833 /* Save and scale the rectangles so get_pos() can place the text */
1834 overlay->ink_rect.x =
1835 ceil ((ink_rect.x - ceil (outline_offset / 2.0l)) * scalef_x);
1836 overlay->ink_rect.y =
1837 ceil ((ink_rect.y - ceil (outline_offset / 2.0l)) * scalef_y);
1838 overlay->ink_rect.width = width;
1839 overlay->ink_rect.height = height;
1840
1841 overlay->logical_rect.x =
1842 ceil ((logical_rect.x - ceil (outline_offset / 2.0l)) * scalef_x);
1843 overlay->logical_rect.y =
1844 ceil ((logical_rect.y - ceil (outline_offset / 2.0l)) * scalef_y);
1845 overlay->logical_rect.width =
1846 ceil ((logical_rect.width + shadow_offset + outline_offset) * scalef_x);
1847 overlay->logical_rect.height =
1848 ceil ((logical_rect.height + shadow_offset + outline_offset) * scalef_y);
1849
1850 /* flip the rectangle if doing vertical render */
1851 if (overlay->use_vertical_render) {
1852 PangoRectangle tmp = overlay->ink_rect;
1853
1854 overlay->ink_rect.x = tmp.y;
1855 overlay->ink_rect.y = tmp.x;
1856 overlay->ink_rect.width = tmp.height;
1857 overlay->ink_rect.height = tmp.width;
1858 /* We want the top left corect, but we now have the top right */
1859 overlay->ink_rect.x += overlay->ink_rect.width;
1860
1861 tmp = overlay->logical_rect;
1862 overlay->logical_rect.x = tmp.y;
1863 overlay->logical_rect.y = tmp.x;
1864 overlay->logical_rect.width = tmp.height;
1865 overlay->logical_rect.height = tmp.width;
1866 overlay->logical_rect.x += overlay->logical_rect.width;
1867 }
1868
1869 /* scale to reported window size */
1870 width = ceil (width * overlay->render_scale);
1871 height = ceil (height * overlay->render_scale);
1872 scalef_x *= overlay->render_scale;
1873 scalef_y *= overlay->render_scale;
1874
1875 if (width <= 0 || height <= 0) {
1876 GST_DEBUG_OBJECT (overlay,
1877 "Overlay is outside video frame. Skipping text rendering");
1878 return;
1879 }
1880
1881 if (unscaled_height <= 0 || unscaled_width <= 0) {
1882 GST_DEBUG_OBJECT (overlay,
1883 "Overlay is outside video frame. Skipping text rendering");
1884 return;
1885 }
1886 /* Prepare the transformation matrix. Note that the transformation happens
1887 * in reverse order. So for horizontal text, we will translate and then
1888 * scale. This is important to understand which scale shall be used. */
1889 /* So, as this init'ed scale happens last, when the rectangle has already
1890 * been rotated, the scaling applied to text height (up to now),
1891 * has to be applied along the x-axis */
1892 if (overlay->use_vertical_render) {
1893 double tmp;
1894
1895 tmp = scalef_x;
1896 scalef_x = scalef_y;
1897 scalef_y = tmp;
1898 }
1899 cairo_matrix_init_scale (&cairo_matrix, scalef_x, scalef_y);
1900
1901 if (overlay->use_vertical_render) {
1902 gint tmp;
1903
1904 /* tranlate to the center of the image, rotate, and tranlate the rotated
1905 * image back to the right place */
1906 cairo_matrix_translate (&cairo_matrix, unscaled_height / 2.0l,
1907 unscaled_width / 2.0l);
1908 /* 90 degree clockwise rotation which is PI / 2 in radiants */
1909 cairo_matrix_rotate (&cairo_matrix, G_PI_2);
1910 cairo_matrix_translate (&cairo_matrix, -(unscaled_width / 2.0l),
1911 -(unscaled_height / 2.0l));
1912
1913 /* Swap width and height */
1914 tmp = height;
1915 height = width;
1916 width = tmp;
1917 }
1918
1919 cairo_matrix_translate (&cairo_matrix,
1920 ceil (outline_offset / 2.0l) - ink_rect.x,
1921 ceil (outline_offset / 2.0l) - ink_rect.y);
1922
1923 /* reallocate overlay buffer */
1924 buffer = gst_buffer_new_and_alloc (4 * width * height);
1925 gst_buffer_replace (&overlay->text_image, buffer);
1926 gst_buffer_unref (buffer);
1927
1928 gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1929 surface = cairo_image_surface_create_for_data (map.data,
1930 CAIRO_FORMAT_ARGB32, width, height, width * 4);
1931 cr = cairo_create (surface);
1932
1933 /* clear surface */
1934 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1935 cairo_paint (cr);
1936
1937 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1938
1939 /* apply transformations */
1940 cairo_set_matrix (cr, &cairo_matrix);
1941
1942 /* FIXME: We use show_layout everywhere except for the surface
1943 * because it's really faster and internally does all kinds of
1944 * caching. Unfortunately we have to paint to a cairo path for
1945 * the outline and this is slow. Once Pango supports user fonts
1946 * we should use them, see
1947 * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1948 *
1949 * Idea would the be, to create a cairo user font that
1950 * does shadow, outline, text painting in the
1951 * render_glyph function.
1952 */
1953
1954 /* draw shadow text */
1955 if (overlay->draw_shadow) {
1956 PangoAttrList *origin_attr, *filtered_attr, *temp_attr;
1957
1958 /* Store a ref on the original attributes for later restoration */
1959 origin_attr =
1960 pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1961 /* Take a copy of the original attributes, because pango_attr_list_filter
1962 * modifies the passed list */
1963 temp_attr = pango_attr_list_copy (origin_attr);
1964 filtered_attr =
1965 pango_attr_list_filter (temp_attr,
1966 gst_text_overlay_filter_foreground_attr, NULL);
1967 pango_attr_list_unref (temp_attr);
1968
1969 cairo_save (cr);
1970 cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1971 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1972 pango_layout_set_attributes (overlay->layout, filtered_attr);
1973 pango_cairo_show_layout (cr, overlay->layout);
1974 pango_layout_set_attributes (overlay->layout, origin_attr);
1975 pango_attr_list_unref (filtered_attr);
1976 pango_attr_list_unref (origin_attr);
1977 cairo_restore (cr);
1978 }
1979
1980 /* draw outline text */
1981 if (overlay->draw_outline) {
1982 a = (overlay->outline_color >> 24) & 0xff;
1983 r = (overlay->outline_color >> 16) & 0xff;
1984 g = (overlay->outline_color >> 8) & 0xff;
1985 b = (overlay->outline_color >> 0) & 0xff;
1986
1987 cairo_save (cr);
1988 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1989 cairo_set_line_width (cr, overlay->outline_offset);
1990 pango_cairo_layout_path (cr, overlay->layout);
1991 cairo_stroke (cr);
1992 cairo_restore (cr);
1993 }
1994
1995 a = (overlay->color >> 24) & 0xff;
1996 r = (overlay->color >> 16) & 0xff;
1997 g = (overlay->color >> 8) & 0xff;
1998 b = (overlay->color >> 0) & 0xff;
1999
2000 /* draw text */
2001 cairo_save (cr);
2002 cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
2003 pango_cairo_show_layout (cr, overlay->layout);
2004 cairo_restore (cr);
2005
2006 cairo_destroy (cr);
2007 cairo_surface_destroy (surface);
2008 gst_buffer_unmap (buffer, &map);
2009 if (width != 0)
2010 overlay->text_width = width;
2011 if (height != 0)
2012 overlay->text_height = height;
2013
2014 gst_base_text_overlay_set_composition (overlay);
2015 }
2016
2017 static inline void
gst_base_text_overlay_shade_planar_Y(GstBaseTextOverlay * overlay,GstVideoFrame * dest,gint x0,gint x1,gint y0,gint y1)2018 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
2019 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
2020 {
2021 gint i, j, dest_stride;
2022 guint8 *dest_ptr;
2023
2024 dest_stride = dest->info.stride[0];
2025 dest_ptr = dest->data[0];
2026
2027 for (i = y0; i < y1; ++i) {
2028 for (j = x0; j < x1; ++j) {
2029 gint y = dest_ptr[(i * dest_stride) + j] - overlay->shading_value;
2030
2031 dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
2032 }
2033 }
2034 }
2035
2036 static inline void
gst_base_text_overlay_shade_packed_Y(GstBaseTextOverlay * overlay,GstVideoFrame * dest,gint x0,gint x1,gint y0,gint y1)2037 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
2038 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
2039 {
2040 gint i, j;
2041 guint dest_stride, pixel_stride;
2042 guint8 *dest_ptr;
2043
2044 dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
2045 dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
2046 pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
2047
2048 if (x0 != 0)
2049 x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
2050 if (x1 != 0)
2051 x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
2052
2053 if (y0 != 0)
2054 y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
2055 if (y1 != 0)
2056 y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
2057
2058 for (i = y0; i < y1; i++) {
2059 for (j = x0; j < x1; j++) {
2060 gint y;
2061 gint y_pos;
2062
2063 y_pos = (i * dest_stride) + j * pixel_stride;
2064 y = dest_ptr[y_pos] - overlay->shading_value;
2065
2066 dest_ptr[y_pos] = CLAMP (y, 0, 255);
2067 }
2068 }
2069 }
2070
2071 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
2072 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
2073 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
2074 static inline void
gst_base_text_overlay_shade_xRGB(GstBaseTextOverlay * overlay,GstVideoFrame * dest,gint x0,gint x1,gint y0,gint y1)2075 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
2076 GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
2077 {
2078 gint i, j;
2079 guint8 *dest_ptr;
2080
2081 dest_ptr = dest->data[0];
2082
2083 for (i = y0; i < y1; i++) {
2084 for (j = x0; j < x1; j++) {
2085 gint y, y_pos, k;
2086
2087 y_pos = (i * 4 * overlay->width) + j * 4;
2088 for (k = 0; k < 4; k++) {
2089 y = dest_ptr[y_pos + k] - overlay->shading_value;
2090 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
2091 }
2092 }
2093 }
2094 }
2095
2096 /* FIXME: orcify */
2097 static void
gst_base_text_overlay_shade_rgb24(GstBaseTextOverlay * overlay,GstVideoFrame * frame,gint x0,gint x1,gint y0,gint y1)2098 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
2099 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2100 {
2101 const int pstride = 3;
2102 gint y, x, stride, shading_val, tmp;
2103 guint8 *p;
2104
2105 shading_val = -overlay->shading_value;
2106 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
2107
2108 for (y = y0; y < y1; ++y) {
2109 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
2110 p += (y * stride) + (x0 * pstride);
2111 for (x = x0; x < x1; ++x) {
2112 tmp = *p + shading_val;
2113 *p++ = CLAMP (tmp, 0, 255);
2114 tmp = *p + shading_val;
2115 *p++ = CLAMP (tmp, 0, 255);
2116 tmp = *p + shading_val;
2117 *p++ = CLAMP (tmp, 0, 255);
2118 }
2119 }
2120 }
2121
2122 static void
gst_base_text_overlay_shade_IYU1(GstBaseTextOverlay * overlay,GstVideoFrame * frame,gint x0,gint x1,gint y0,gint y1)2123 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
2124 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2125 {
2126 gint y, x, stride, shading_val, tmp;
2127 guint8 *p;
2128
2129 shading_val = -overlay->shading_value;
2130 stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
2131
2132 /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
2133 for (y = y0; y < y1; ++y) {
2134 p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
2135 /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
2136 /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
2137 * unlikely anyone will notice.. */
2138 p += (y * stride) + ((x0 / 2) * 3) + 1;
2139 for (x = x0; x < x1; x += 2) {
2140 tmp = *p + shading_val;
2141 *p++ = CLAMP (tmp, 0, 255);
2142 tmp = *p + shading_val;
2143 *p++ = CLAMP (tmp, 0, 255);
2144 /* skip chroma */
2145 p++;
2146 }
2147 }
2148 }
2149
2150 #define ARGB_SHADE_FUNCTION(name, OFFSET) \
2151 static inline void \
2152 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
2153 gint x0, gint x1, gint y0, gint y1) \
2154 { \
2155 gint i, j;\
2156 guint8 *dest_ptr;\
2157 \
2158 dest_ptr = dest->data[0];\
2159 \
2160 for (i = y0; i < y1; i++) {\
2161 for (j = x0; j < x1; j++) {\
2162 gint y, y_pos, k;\
2163 y_pos = (i * 4 * overlay->width) + j * 4;\
2164 for (k = OFFSET; k < 3+OFFSET; k++) {\
2165 y = dest_ptr[y_pos + k] - overlay->shading_value;\
2166 dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
2167 }\
2168 }\
2169 }\
2170 }
2171 ARGB_SHADE_FUNCTION (ARGB, 1);
2172 ARGB_SHADE_FUNCTION (ABGR, 1);
2173 ARGB_SHADE_FUNCTION (RGBA, 0);
2174 ARGB_SHADE_FUNCTION (BGRA, 0);
2175
2176 static void
gst_base_text_overlay_render_text(GstBaseTextOverlay * overlay,const gchar * text,gint textlen)2177 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
2178 const gchar * text, gint textlen)
2179 {
2180 gchar *string;
2181
2182 if (!overlay->need_render) {
2183 GST_DEBUG ("Using previously rendered text.");
2184 return;
2185 }
2186
2187 /* -1 is the whole string */
2188 if (text != NULL && textlen < 0) {
2189 textlen = strlen (text);
2190 }
2191
2192 if (text != NULL) {
2193 string = g_strndup (text, textlen);
2194 } else { /* empty string */
2195 string = g_strdup (" ");
2196 }
2197 g_strdelimit (string, "\r\t", ' ');
2198 textlen = strlen (string);
2199
2200 /* FIXME: should we check for UTF-8 here? */
2201
2202 GST_DEBUG ("Rendering '%s'", string);
2203 gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
2204
2205 g_free (string);
2206
2207 overlay->need_render = FALSE;
2208 }
2209
2210 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
2211 #define BOX_XPAD 6
2212 #define BOX_YPAD 6
2213
2214 static void
gst_base_text_overlay_shade_background(GstBaseTextOverlay * overlay,GstVideoFrame * frame,gint x0,gint x1,gint y0,gint y1)2215 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
2216 GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2217 {
2218 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
2219 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
2220
2221 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
2222 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
2223
2224 switch (overlay->format) {
2225 case GST_VIDEO_FORMAT_I420:
2226 case GST_VIDEO_FORMAT_YV12:
2227 case GST_VIDEO_FORMAT_NV12:
2228 case GST_VIDEO_FORMAT_NV21:
2229 case GST_VIDEO_FORMAT_Y41B:
2230 case GST_VIDEO_FORMAT_Y42B:
2231 case GST_VIDEO_FORMAT_Y444:
2232 case GST_VIDEO_FORMAT_YUV9:
2233 case GST_VIDEO_FORMAT_YVU9:
2234 case GST_VIDEO_FORMAT_GRAY8:
2235 case GST_VIDEO_FORMAT_A420:
2236 gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
2237 break;
2238 case GST_VIDEO_FORMAT_AYUV:
2239 case GST_VIDEO_FORMAT_UYVY:
2240 case GST_VIDEO_FORMAT_VYUY:
2241 case GST_VIDEO_FORMAT_YUY2:
2242 case GST_VIDEO_FORMAT_v308:
2243 case GST_VIDEO_FORMAT_IYU2:
2244 gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
2245 break;
2246 case GST_VIDEO_FORMAT_xRGB:
2247 gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
2248 break;
2249 case GST_VIDEO_FORMAT_xBGR:
2250 gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
2251 break;
2252 case GST_VIDEO_FORMAT_BGRx:
2253 gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
2254 break;
2255 case GST_VIDEO_FORMAT_RGBx:
2256 gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
2257 break;
2258 case GST_VIDEO_FORMAT_ARGB:
2259 gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
2260 break;
2261 case GST_VIDEO_FORMAT_ABGR:
2262 gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
2263 break;
2264 case GST_VIDEO_FORMAT_RGBA:
2265 gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
2266 break;
2267 case GST_VIDEO_FORMAT_BGRA:
2268 gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
2269 break;
2270 case GST_VIDEO_FORMAT_BGR:
2271 case GST_VIDEO_FORMAT_RGB:
2272 gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
2273 break;
2274 case GST_VIDEO_FORMAT_IYU1:
2275 gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
2276 break;
2277 default:
2278 GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
2279 gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
2280 break;
2281 }
2282 }
2283
2284 static GstFlowReturn
gst_base_text_overlay_push_frame(GstBaseTextOverlay * overlay,GstBuffer * video_frame)2285 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
2286 GstBuffer * video_frame)
2287 {
2288 GstVideoFrame frame;
2289
2290 if (overlay->composition == NULL)
2291 goto done;
2292
2293 if (gst_pad_check_reconfigure (overlay->srcpad)) {
2294 if (!gst_base_text_overlay_negotiate (overlay, NULL)) {
2295 gst_pad_mark_reconfigure (overlay->srcpad);
2296 gst_buffer_unref (video_frame);
2297 if (GST_PAD_IS_FLUSHING (overlay->srcpad))
2298 return GST_FLOW_FLUSHING;
2299 else
2300 return GST_FLOW_NOT_NEGOTIATED;
2301 }
2302 }
2303
2304 video_frame = gst_buffer_make_writable (video_frame);
2305
2306 if (overlay->attach_compo_to_buffer) {
2307 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
2308 gst_buffer_add_video_overlay_composition_meta (video_frame,
2309 overlay->composition);
2310 /* FIXME: emulate shaded background box if want_shading=true */
2311 goto done;
2312 }
2313
2314 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
2315 GST_MAP_READWRITE))
2316 goto invalid_frame;
2317
2318 /* shaded background box */
2319 if (overlay->want_shading) {
2320 gint xpos, ypos;
2321
2322 gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
2323
2324 gst_base_text_overlay_shade_background (overlay, &frame,
2325 xpos, xpos + overlay->text_width, ypos, ypos + overlay->text_height);
2326 }
2327
2328 gst_video_overlay_composition_blend (overlay->composition, &frame);
2329
2330 gst_video_frame_unmap (&frame);
2331
2332 done:
2333
2334 return gst_pad_push (overlay->srcpad, video_frame);
2335
2336 /* ERRORS */
2337 invalid_frame:
2338 {
2339 gst_buffer_unref (video_frame);
2340 GST_DEBUG_OBJECT (overlay, "received invalid buffer");
2341 return GST_FLOW_OK;
2342 }
2343 }
2344
2345 static GstPadLinkReturn
gst_base_text_overlay_text_pad_link(GstPad * pad,GstObject * parent,GstPad * peer)2346 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
2347 GstPad * peer)
2348 {
2349 GstBaseTextOverlay *overlay;
2350
2351 overlay = GST_BASE_TEXT_OVERLAY (parent);
2352 if (G_UNLIKELY (!overlay))
2353 return GST_PAD_LINK_REFUSED;
2354
2355 GST_DEBUG_OBJECT (overlay, "Text pad linked");
2356
2357 overlay->text_linked = TRUE;
2358
2359 return GST_PAD_LINK_OK;
2360 }
2361
2362 static void
gst_base_text_overlay_text_pad_unlink(GstPad * pad,GstObject * parent)2363 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
2364 {
2365 GstBaseTextOverlay *overlay;
2366
2367 /* don't use gst_pad_get_parent() here, will deadlock */
2368 overlay = GST_BASE_TEXT_OVERLAY (parent);
2369
2370 GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
2371
2372 overlay->text_linked = FALSE;
2373
2374 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
2375 }
2376
2377 static gboolean
gst_base_text_overlay_text_event(GstPad * pad,GstObject * parent,GstEvent * event)2378 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
2379 GstEvent * event)
2380 {
2381 gboolean ret = FALSE;
2382 GstBaseTextOverlay *overlay = NULL;
2383
2384 overlay = GST_BASE_TEXT_OVERLAY (parent);
2385
2386 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2387
2388 switch (GST_EVENT_TYPE (event)) {
2389 case GST_EVENT_CAPS:
2390 {
2391 GstCaps *caps;
2392
2393 gst_event_parse_caps (event, &caps);
2394 ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
2395 gst_event_unref (event);
2396 break;
2397 }
2398 case GST_EVENT_SEGMENT:
2399 {
2400 const GstSegment *segment;
2401
2402 overlay->text_eos = FALSE;
2403
2404 gst_event_parse_segment (event, &segment);
2405
2406 if (segment->format == GST_FORMAT_TIME) {
2407 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2408 gst_segment_copy_into (segment, &overlay->text_segment);
2409 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
2410 &overlay->text_segment);
2411 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2412 } else {
2413 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2414 ("received non-TIME newsegment event on text input"));
2415 }
2416
2417 gst_event_unref (event);
2418 ret = TRUE;
2419
2420 /* wake up the video chain, it might be waiting for a text buffer or
2421 * a text segment update */
2422 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2423 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2424 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2425 break;
2426 }
2427 case GST_EVENT_GAP:
2428 {
2429 GstClockTime start, duration;
2430
2431 gst_event_parse_gap (event, &start, &duration);
2432 if (GST_CLOCK_TIME_IS_VALID (duration))
2433 start += duration;
2434 /* we do not expect another buffer until after gap,
2435 * so that is our position now */
2436 overlay->text_segment.position = start;
2437
2438 /* wake up the video chain, it might be waiting for a text buffer or
2439 * a text segment update */
2440 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2441 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2442 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2443
2444 gst_event_unref (event);
2445 ret = TRUE;
2446 break;
2447 }
2448 case GST_EVENT_FLUSH_STOP:
2449 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2450 GST_INFO_OBJECT (overlay, "text flush stop");
2451 overlay->text_flushing = FALSE;
2452 overlay->text_eos = FALSE;
2453 gst_base_text_overlay_pop_text (overlay);
2454 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2455 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2456 gst_event_unref (event);
2457 ret = TRUE;
2458 break;
2459 case GST_EVENT_FLUSH_START:
2460 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2461 GST_INFO_OBJECT (overlay, "text flush start");
2462 overlay->text_flushing = TRUE;
2463 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2464 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2465 gst_event_unref (event);
2466 ret = TRUE;
2467 break;
2468 case GST_EVENT_EOS:
2469 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2470 overlay->text_eos = TRUE;
2471 GST_INFO_OBJECT (overlay, "text EOS");
2472 /* wake up the video chain, it might be waiting for a text buffer or
2473 * a text segment update */
2474 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2475 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2476 gst_event_unref (event);
2477 ret = TRUE;
2478 break;
2479 default:
2480 ret = gst_pad_event_default (pad, parent, event);
2481 break;
2482 }
2483
2484 return ret;
2485 }
2486
2487 static gboolean
gst_base_text_overlay_video_event(GstPad * pad,GstObject * parent,GstEvent * event)2488 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
2489 GstEvent * event)
2490 {
2491 gboolean ret = FALSE;
2492 GstBaseTextOverlay *overlay = NULL;
2493
2494 overlay = GST_BASE_TEXT_OVERLAY (parent);
2495
2496 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2497
2498 switch (GST_EVENT_TYPE (event)) {
2499 case GST_EVENT_CAPS:
2500 {
2501 GstCaps *caps;
2502
2503 gst_event_parse_caps (event, &caps);
2504 ret = gst_base_text_overlay_setcaps (overlay, caps);
2505 gst_event_unref (event);
2506 break;
2507 }
2508 case GST_EVENT_SEGMENT:
2509 {
2510 const GstSegment *segment;
2511
2512 GST_DEBUG_OBJECT (overlay, "received new segment");
2513
2514 gst_event_parse_segment (event, &segment);
2515
2516 if (segment->format == GST_FORMAT_TIME) {
2517 gst_segment_copy_into (segment, &overlay->segment);
2518 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
2519 &overlay->segment);
2520 } else {
2521 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2522 ("received non-TIME newsegment event on video input"));
2523 }
2524
2525 ret = gst_pad_event_default (pad, parent, event);
2526 break;
2527 }
2528 case GST_EVENT_EOS:
2529 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2530 GST_INFO_OBJECT (overlay, "video EOS");
2531 overlay->video_eos = TRUE;
2532 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2533 ret = gst_pad_event_default (pad, parent, event);
2534 break;
2535 case GST_EVENT_FLUSH_START:
2536 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2537 GST_INFO_OBJECT (overlay, "video flush start");
2538 overlay->video_flushing = TRUE;
2539 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2540 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2541 ret = gst_pad_event_default (pad, parent, event);
2542 break;
2543 case GST_EVENT_FLUSH_STOP:
2544 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2545 GST_INFO_OBJECT (overlay, "video flush stop");
2546 overlay->video_flushing = FALSE;
2547 overlay->video_eos = FALSE;
2548 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2549 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2550 ret = gst_pad_event_default (pad, parent, event);
2551 break;
2552 default:
2553 ret = gst_pad_event_default (pad, parent, event);
2554 break;
2555 }
2556
2557 return ret;
2558 }
2559
2560 static gboolean
gst_base_text_overlay_video_query(GstPad * pad,GstObject * parent,GstQuery * query)2561 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
2562 GstQuery * query)
2563 {
2564 gboolean ret = FALSE;
2565 GstBaseTextOverlay *overlay;
2566
2567 overlay = GST_BASE_TEXT_OVERLAY (parent);
2568
2569 switch (GST_QUERY_TYPE (query)) {
2570 case GST_QUERY_CAPS:
2571 {
2572 GstCaps *filter, *caps;
2573
2574 gst_query_parse_caps (query, &filter);
2575 caps = gst_base_text_overlay_get_videosink_caps (pad, overlay, filter);
2576 gst_query_set_caps_result (query, caps);
2577 gst_caps_unref (caps);
2578 ret = TRUE;
2579 break;
2580 }
2581 default:
2582 ret = gst_pad_query_default (pad, parent, query);
2583 break;
2584 }
2585
2586 return ret;
2587 }
2588
2589 /* Called with lock held */
2590 static void
gst_base_text_overlay_pop_text(GstBaseTextOverlay * overlay)2591 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2592 {
2593 g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2594
2595 if (overlay->text_buffer) {
2596 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2597 overlay->text_buffer);
2598 gst_buffer_unref (overlay->text_buffer);
2599 overlay->text_buffer = NULL;
2600 }
2601
2602 /* Let the text task know we used that buffer */
2603 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2604 }
2605
2606 /* We receive text buffers here. If they are out of segment we just ignore them.
2607 If the buffer is in our segment we keep it internally except if another one
2608 is already waiting here, in that case we wait that it gets kicked out */
2609 static GstFlowReturn
gst_base_text_overlay_text_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)2610 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
2611 GstBuffer * buffer)
2612 {
2613 GstFlowReturn ret = GST_FLOW_OK;
2614 GstBaseTextOverlay *overlay = NULL;
2615 gboolean in_seg = FALSE;
2616 guint64 clip_start = 0, clip_stop = 0;
2617
2618 overlay = GST_BASE_TEXT_OVERLAY (parent);
2619
2620 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2621
2622 if (overlay->text_flushing) {
2623 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2624 ret = GST_FLOW_FLUSHING;
2625 GST_LOG_OBJECT (overlay, "text flushing");
2626 goto beach;
2627 }
2628
2629 if (overlay->text_eos) {
2630 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2631 ret = GST_FLOW_EOS;
2632 GST_LOG_OBJECT (overlay, "text EOS");
2633 goto beach;
2634 }
2635
2636 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2637 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2638 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2639 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2640 GST_BUFFER_DURATION (buffer)));
2641
2642 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2643 GstClockTime stop;
2644
2645 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2646 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2647 else
2648 stop = GST_CLOCK_TIME_NONE;
2649
2650 in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2651 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2652 } else {
2653 in_seg = TRUE;
2654 }
2655
2656 if (in_seg) {
2657 /* about to change metadata */
2658 buffer = gst_buffer_make_writable (buffer);
2659 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2660 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2661 if (GST_BUFFER_DURATION_IS_VALID (buffer))
2662 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2663
2664 /* Wait for the previous buffer to go away */
2665 while (overlay->text_buffer != NULL) {
2666 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2667 GST_DEBUG_PAD_NAME (pad));
2668 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2669 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2670 if (overlay->text_flushing) {
2671 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2672 ret = GST_FLOW_FLUSHING;
2673 goto beach;
2674 }
2675 }
2676
2677 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2678 overlay->text_segment.position = clip_start;
2679
2680 overlay->text_buffer = buffer; /* pass ownership of @buffer */
2681 buffer = NULL;
2682
2683 /* That's a new text buffer we need to render */
2684 overlay->need_render = TRUE;
2685
2686 /* in case the video chain is waiting for a text buffer, wake it up */
2687 GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2688 }
2689
2690 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2691
2692 beach:
2693 if (buffer)
2694 gst_buffer_unref (buffer);
2695
2696 return ret;
2697 }
2698
2699 static GstFlowReturn
gst_base_text_overlay_video_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)2700 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2701 GstBuffer * buffer)
2702 {
2703 GstBaseTextOverlayClass *klass;
2704 GstBaseTextOverlay *overlay;
2705 GstFlowReturn ret = GST_FLOW_OK;
2706 gboolean in_seg = FALSE;
2707 guint64 start, stop, clip_start = 0, clip_stop = 0;
2708 gchar *text = NULL;
2709 GstVideoOverlayCompositionMeta *composition_meta;
2710
2711 overlay = GST_BASE_TEXT_OVERLAY (parent);
2712
2713 composition_meta = gst_buffer_get_video_overlay_composition_meta (buffer);
2714 if (composition_meta) {
2715 if (overlay->upstream_composition != composition_meta->overlay) {
2716 GST_DEBUG ("GstVideoOverlayCompositionMeta found.");
2717 overlay->upstream_composition = composition_meta->overlay;
2718 overlay->need_render = TRUE;
2719 }
2720 } else if (overlay->upstream_composition != NULL) {
2721 overlay->upstream_composition = NULL;
2722 overlay->need_render = TRUE;
2723 }
2724
2725 klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2726
2727 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2728 goto missing_timestamp;
2729
2730 /* ignore buffers that are outside of the current segment */
2731 start = GST_BUFFER_TIMESTAMP (buffer);
2732
2733 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2734 stop = GST_CLOCK_TIME_NONE;
2735 } else {
2736 stop = start + GST_BUFFER_DURATION (buffer);
2737 }
2738
2739 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
2740 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2741 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2742
2743 /* segment_clip() will adjust start unconditionally to segment_start if
2744 * no stop time is provided, so handle this ourselves */
2745 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2746 goto out_of_segment;
2747
2748 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2749 &clip_start, &clip_stop);
2750
2751 if (!in_seg)
2752 goto out_of_segment;
2753
2754 /* if the buffer is only partially in the segment, fix up stamps */
2755 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2756 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2757 buffer = gst_buffer_make_writable (buffer);
2758 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2759 if (stop != -1)
2760 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2761 }
2762
2763 /* now, after we've done the clipping, fix up end time if there's no
2764 * duration (we only use those estimated values internally though, we
2765 * don't want to set bogus values on the buffer itself) */
2766 if (stop == -1) {
2767 if (overlay->info.fps_n && overlay->info.fps_d) {
2768 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2769 stop = start + gst_util_uint64_scale_int (GST_SECOND,
2770 overlay->info.fps_d, overlay->info.fps_n);
2771 } else {
2772 GST_LOG_OBJECT (overlay, "no duration, assuming minimal duration");
2773 stop = start + 1; /* we need to assume some interval */
2774 }
2775 }
2776
2777 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2778
2779 wait_for_text_buf:
2780
2781 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2782
2783 if (overlay->video_flushing)
2784 goto flushing;
2785
2786 if (overlay->video_eos)
2787 goto have_eos;
2788
2789 if (overlay->silent) {
2790 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2791 ret = gst_pad_push (overlay->srcpad, buffer);
2792
2793 /* Update position */
2794 overlay->segment.position = clip_start;
2795
2796 return ret;
2797 }
2798
2799 /* Text pad not linked, rendering internal text */
2800 if (!overlay->text_linked) {
2801 if (klass->get_text) {
2802 text = klass->get_text (overlay, buffer);
2803 } else {
2804 text = g_strdup (overlay->default_text);
2805 }
2806
2807 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2808 "text: '%s'", GST_STR_NULL (text));
2809
2810 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2811
2812 if (text != NULL && *text != '\0') {
2813 /* Render and push */
2814 gst_base_text_overlay_render_text (overlay, text, -1);
2815 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2816 } else {
2817 /* Invalid or empty string */
2818 ret = gst_pad_push (overlay->srcpad, buffer);
2819 }
2820 } else {
2821 /* Text pad linked, check if we have a text buffer queued */
2822 if (overlay->text_buffer) {
2823 gboolean pop_text = FALSE, valid_text_time = TRUE;
2824 GstClockTime text_start = GST_CLOCK_TIME_NONE;
2825 GstClockTime text_end = GST_CLOCK_TIME_NONE;
2826 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2827 GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2828 GstClockTime vid_running_time, vid_running_time_end;
2829
2830 /* if the text buffer isn't stamped right, pop it off the
2831 * queue and display it for the current video frame only */
2832 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2833 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2834 GST_WARNING_OBJECT (overlay,
2835 "Got text buffer with invalid timestamp or duration");
2836 pop_text = TRUE;
2837 valid_text_time = FALSE;
2838 } else {
2839 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2840 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2841 }
2842
2843 vid_running_time =
2844 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2845 start);
2846 vid_running_time_end =
2847 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2848 stop);
2849
2850 /* If timestamp and duration are valid */
2851 if (valid_text_time) {
2852 text_running_time =
2853 gst_segment_to_running_time (&overlay->text_segment,
2854 GST_FORMAT_TIME, text_start);
2855 text_running_time_end =
2856 gst_segment_to_running_time (&overlay->text_segment,
2857 GST_FORMAT_TIME, text_end);
2858 }
2859
2860 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2861 GST_TIME_ARGS (text_running_time),
2862 GST_TIME_ARGS (text_running_time_end));
2863 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2864 GST_TIME_ARGS (vid_running_time),
2865 GST_TIME_ARGS (vid_running_time_end));
2866
2867 /* Text too old or in the future */
2868 if (valid_text_time && text_running_time_end <= vid_running_time) {
2869 /* text buffer too old, get rid of it and do nothing */
2870 GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2871 pop_text = FALSE;
2872 gst_base_text_overlay_pop_text (overlay);
2873 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2874 goto wait_for_text_buf;
2875 } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2876 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2877 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2878 /* Push the video frame */
2879 ret = gst_pad_push (overlay->srcpad, buffer);
2880 } else {
2881 GstMapInfo map;
2882 gchar *in_text;
2883 gsize in_size;
2884
2885 gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2886 in_text = (gchar *) map.data;
2887 in_size = map.size;
2888
2889 if (in_size > 0) {
2890 /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2891 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2892 * here on purpose, this is something that needs fixing upstream */
2893 if (!g_utf8_validate (in_text, in_size, NULL)) {
2894 const gchar *end = NULL;
2895
2896 GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2897 in_text = g_strndup (in_text, in_size);
2898 while (!g_utf8_validate (in_text, in_size, &end) && end)
2899 *((gchar *) end) = '*';
2900 }
2901
2902 /* Get the string */
2903 if (overlay->have_pango_markup) {
2904 text = g_strndup (in_text, in_size);
2905 } else {
2906 text = g_markup_escape_text (in_text, in_size);
2907 }
2908
2909 if (text != NULL && *text != '\0') {
2910 gint text_len = strlen (text);
2911
2912 while (text_len > 0 && (text[text_len - 1] == '\n' ||
2913 text[text_len - 1] == '\r')) {
2914 --text_len;
2915 }
2916 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2917 gst_base_text_overlay_render_text (overlay, text, text_len);
2918 } else {
2919 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2920 gst_base_text_overlay_render_text (overlay, " ", 1);
2921 }
2922 if (in_text != (gchar *) map.data)
2923 g_free (in_text);
2924 } else {
2925 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2926 gst_base_text_overlay_render_text (overlay, " ", 1);
2927 }
2928
2929 gst_buffer_unmap (overlay->text_buffer, &map);
2930
2931 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2932 ret = gst_base_text_overlay_push_frame (overlay, buffer);
2933
2934 if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2935 GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2936 pop_text = TRUE;
2937 }
2938 }
2939 if (pop_text) {
2940 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2941 gst_base_text_overlay_pop_text (overlay);
2942 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2943 }
2944 } else {
2945 gboolean wait_for_text_buf = TRUE;
2946
2947 if (overlay->text_eos)
2948 wait_for_text_buf = FALSE;
2949
2950 if (!overlay->wait_text)
2951 wait_for_text_buf = FALSE;
2952
2953 /* Text pad linked, but no text buffer available - what now? */
2954 if (overlay->text_segment.format == GST_FORMAT_TIME) {
2955 GstClockTime text_start_running_time, text_position_running_time;
2956 GstClockTime vid_running_time;
2957
2958 vid_running_time =
2959 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2960 GST_BUFFER_TIMESTAMP (buffer));
2961 text_start_running_time =
2962 gst_segment_to_running_time (&overlay->text_segment,
2963 GST_FORMAT_TIME, overlay->text_segment.start);
2964 text_position_running_time =
2965 gst_segment_to_running_time (&overlay->text_segment,
2966 GST_FORMAT_TIME, overlay->text_segment.position);
2967
2968 if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2969 vid_running_time < text_start_running_time) ||
2970 (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2971 vid_running_time < text_position_running_time)) {
2972 wait_for_text_buf = FALSE;
2973 }
2974 }
2975
2976 if (wait_for_text_buf) {
2977 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2978 GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2979 GST_DEBUG_OBJECT (overlay, "resuming");
2980 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2981 goto wait_for_text_buf;
2982 } else {
2983 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2984 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2985 ret = gst_pad_push (overlay->srcpad, buffer);
2986 }
2987 }
2988 }
2989
2990 g_free (text);
2991
2992 /* Update position */
2993 overlay->segment.position = clip_start;
2994
2995 return ret;
2996
2997 missing_timestamp:
2998 {
2999 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
3000 gst_buffer_unref (buffer);
3001 return GST_FLOW_OK;
3002 }
3003
3004 flushing:
3005 {
3006 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
3007 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
3008 gst_buffer_unref (buffer);
3009 return GST_FLOW_FLUSHING;
3010 }
3011 have_eos:
3012 {
3013 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
3014 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
3015 gst_buffer_unref (buffer);
3016 return GST_FLOW_EOS;
3017 }
3018 out_of_segment:
3019 {
3020 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
3021 gst_buffer_unref (buffer);
3022 return GST_FLOW_OK;
3023 }
3024 }
3025
3026 static GstStateChangeReturn
gst_base_text_overlay_change_state(GstElement * element,GstStateChange transition)3027 gst_base_text_overlay_change_state (GstElement * element,
3028 GstStateChange transition)
3029 {
3030 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
3031 GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
3032
3033 switch (transition) {
3034 case GST_STATE_CHANGE_PAUSED_TO_READY:
3035 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
3036 overlay->text_flushing = TRUE;
3037 overlay->video_flushing = TRUE;
3038 /* pop_text will broadcast on the GCond and thus also make the video
3039 * chain exit if it's waiting for a text buffer */
3040 gst_base_text_overlay_pop_text (overlay);
3041 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
3042 break;
3043 default:
3044 break;
3045 }
3046
3047 ret = parent_class->change_state (element, transition);
3048 if (ret == GST_STATE_CHANGE_FAILURE)
3049 return ret;
3050
3051 switch (transition) {
3052 case GST_STATE_CHANGE_READY_TO_PAUSED:
3053 GST_BASE_TEXT_OVERLAY_LOCK (overlay);
3054 overlay->text_flushing = FALSE;
3055 overlay->video_flushing = FALSE;
3056 overlay->video_eos = FALSE;
3057 overlay->text_eos = FALSE;
3058 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
3059 gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
3060 GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
3061 break;
3062 default:
3063 break;
3064 }
3065
3066 return ret;
3067 }
3068
3069 static gboolean
plugin_init(GstPlugin * plugin)3070 plugin_init (GstPlugin * plugin)
3071 {
3072 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
3073 GST_TYPE_TEXT_OVERLAY) ||
3074 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
3075 GST_TYPE_TIME_OVERLAY) ||
3076 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
3077 GST_TYPE_CLOCK_OVERLAY) ||
3078 !gst_element_register (plugin, "textrender", GST_RANK_NONE,
3079 GST_TYPE_TEXT_RENDER)) {
3080 return FALSE;
3081 }
3082
3083 /*texttestsrc_plugin_init(module, plugin); */
3084
3085 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
3086
3087 return TRUE;
3088 }
3089
3090 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
3091 pango, "Pango-based text rendering and overlay", plugin_init,
3092 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
3093