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