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> Tim-Philipp Müller <tim centricular net> |
|
6 * |
|
7 * This library is free software; you can redistribute it and/or |
|
8 * modify it under the terms of the GNU Library General Public |
|
9 * License as published by the Free Software Foundation; either |
|
10 * version 2 of the License, or (at your option) any later version. |
|
11 * |
|
12 * This library is distributed in the hope that it will be useful, |
|
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
15 * Library General Public License for more details. |
|
16 * |
|
17 * You should have received a copy of the GNU Library General Public |
|
18 * License along with this library; if not, write to the |
|
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
20 * Boston, MA 02111-1307, USA. |
|
21 */ |
|
22 |
|
23 /** |
|
24 * SECTION:element-textoverlay |
|
25 * @see_also: #GstTextRender, #GstClockOverlay, #GstTimeOverlay, #GstSubParse |
|
26 * |
|
27 * <refsect2> |
|
28 * <para> |
|
29 * This plugin renders text on top of a video stream. This can be either |
|
30 * static text or text from buffers received on the text sink pad, e.g. |
|
31 * as produced by the subparse element. If the text sink pad is not linked, |
|
32 * the text set via the "text" property will be rendered. If the text sink |
|
33 * pad is linked, text will be rendered as it is received on that pad, |
|
34 * honouring and matching the buffer timestamps of both input streams. |
|
35 * </para> |
|
36 * <para> |
|
37 * The text can contain newline characters and text wrapping is enabled by |
|
38 * default. |
|
39 * </para> |
|
40 * <para> |
|
41 * Here is a simple pipeline that displays a static text in the top left |
|
42 * corner of the video picture: |
|
43 * <programlisting> |
|
44 * gst-launch -v videotestsrc ! textoverlay text="Room A" valign=top halign=left ! xvimagesink |
|
45 * </programlisting> |
|
46 * </para> |
|
47 * <para> |
|
48 * Here is another pipeline that displays subtitles from an .srt subtitle |
|
49 * file, centered at the bottom of the picture and with a rectangular shading |
|
50 * around the text in the background: |
|
51 * <programlisting> |
|
52 * gst-launch -v filesrc location=subtitles.srt ! subparse ! txt. videotestsrc ! timeoverlay ! textoverlay name=txt shaded-background=yes ! xvimagesink |
|
53 * </programlisting> |
|
54 * If you do not have such a subtitle file, create one looking like this |
|
55 * in a text editor: |
|
56 * <programlisting> |
|
57 * 1 |
|
58 * 00:00:03,000 --> 00:00:05,000 |
|
59 * Hello? (3-5s) |
|
60 * |
|
61 * 2 |
|
62 * 00:00:08,000 --> 00:00:13,000 |
|
63 * Yes, this is a subtitle. Don't |
|
64 * you like it? (8-13s) |
|
65 * |
|
66 * 3 |
|
67 * 00:00:18,826 --> 00:01:02,886 |
|
68 * Uh? What are you talking about? |
|
69 * I don't understand (18-62s) |
|
70 * </programlisting> |
|
71 * </para> |
|
72 * </refsect2> |
|
73 */ |
|
74 |
|
75 /* FIXME: alloc segment as part of instance struct */ |
|
76 |
|
77 #ifdef HAVE_CONFIG_H |
|
78 #include <config.h> |
|
79 #endif |
|
80 |
|
81 #include <gst/video/video.h> |
|
82 |
|
83 #include "gsttextoverlay.h" |
|
84 #include "gsttimeoverlay.h" |
|
85 #include "gstclockoverlay.h" |
|
86 #include "gsttextrender.h" |
|
87 |
|
88 /* FIXME: |
|
89 * - use proper strides and offset for I420 |
|
90 * - if text is wider than the video picture, it does not get |
|
91 * clipped properly during blitting (if wrapping is disabled) |
|
92 * - make 'shading_value' a property (or enum: light/normal/dark/verydark)? |
|
93 */ |
|
94 |
|
95 GST_DEBUG_CATEGORY (pango_debug); |
|
96 #define GST_CAT_DEFAULT pango_debug |
|
97 |
|
98 static const GstElementDetails text_overlay_details = |
|
99 GST_ELEMENT_DETAILS ("Text overlay", |
|
100 "Filter/Editor/Video", |
|
101 "Adds text strings on top of a video buffer", |
|
102 "David Schleef <ds@schleef.org>"); |
|
103 |
|
104 |
|
105 #define DEFAULT_PROP_TEXT "" |
|
106 #define DEFAULT_PROP_SHADING FALSE |
|
107 #define DEFAULT_PROP_VALIGNMENT GST_TEXT_OVERLAY_VALIGN_BASELINE |
|
108 #define DEFAULT_PROP_HALIGNMENT GST_TEXT_OVERLAY_HALIGN_CENTER |
|
109 #define DEFAULT_PROP_VALIGN "baseline" |
|
110 #define DEFAULT_PROP_HALIGN "center" |
|
111 #define DEFAULT_PROP_XPAD 25 |
|
112 #define DEFAULT_PROP_YPAD 25 |
|
113 #define DEFAULT_PROP_DELTAX 0 |
|
114 #define DEFAULT_PROP_DELTAY 0 |
|
115 #define DEFAULT_PROP_WRAP_MODE GST_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR |
|
116 #define DEFAULT_PROP_FONT_DESC "" |
|
117 #define DEFAULT_PROP_SILENT FALSE |
|
118 #define DEFAULT_PROP_LINE_ALIGNMENT GST_TEXT_OVERLAY_LINE_ALIGN_CENTER |
|
119 |
|
120 /* make a property of me */ |
|
121 #define DEFAULT_SHADING_VALUE -80 |
|
122 |
|
123 enum |
|
124 { |
|
125 PROP_0, |
|
126 PROP_TEXT, |
|
127 PROP_SHADING, |
|
128 PROP_VALIGN, /* deprecated */ |
|
129 PROP_HALIGN, /* deprecated */ |
|
130 PROP_HALIGNMENT, |
|
131 PROP_VALIGNMENT, |
|
132 PROP_XPAD, |
|
133 PROP_YPAD, |
|
134 PROP_DELTAX, |
|
135 PROP_DELTAY, |
|
136 PROP_WRAP_MODE, |
|
137 PROP_FONT_DESC, |
|
138 PROP_SILENT, |
|
139 PROP_LINE_ALIGNMENT |
|
140 }; |
|
141 |
|
142 |
|
143 static GstStaticPadTemplate src_template_factory = |
|
144 GST_STATIC_PAD_TEMPLATE ("src", |
|
145 GST_PAD_SRC, |
|
146 GST_PAD_ALWAYS, |
|
147 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420")) |
|
148 ); |
|
149 |
|
150 static GstStaticPadTemplate video_sink_template_factory = |
|
151 GST_STATIC_PAD_TEMPLATE ("video_sink", |
|
152 GST_PAD_SINK, |
|
153 GST_PAD_ALWAYS, |
|
154 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420")) |
|
155 ); |
|
156 |
|
157 static GstStaticPadTemplate text_sink_template_factory = |
|
158 GST_STATIC_PAD_TEMPLATE ("text_sink", |
|
159 GST_PAD_SINK, |
|
160 GST_PAD_ALWAYS, |
|
161 GST_STATIC_CAPS ("text/x-pango-markup; text/plain") |
|
162 ); |
|
163 |
|
164 |
|
165 #define GST_TYPE_TEXT_OVERLAY_VALIGN (gst_text_overlay_valign_get_type()) |
|
166 static GType |
|
167 gst_text_overlay_valign_get_type (void) |
|
168 { |
|
169 static GType text_overlay_valign_type = 0; |
|
170 static const GEnumValue text_overlay_valign[] = { |
|
171 {GST_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"}, |
|
172 {GST_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"}, |
|
173 {GST_TEXT_OVERLAY_VALIGN_TOP, "top", "top"}, |
|
174 {0, NULL, NULL}, |
|
175 }; |
|
176 |
|
177 if (!text_overlay_valign_type) { |
|
178 text_overlay_valign_type = |
|
179 g_enum_register_static ("GstTextOverlayVAlign", text_overlay_valign); |
|
180 } |
|
181 return text_overlay_valign_type; |
|
182 } |
|
183 |
|
184 #define GST_TYPE_TEXT_OVERLAY_HALIGN (gst_text_overlay_halign_get_type()) |
|
185 static GType |
|
186 gst_text_overlay_halign_get_type (void) |
|
187 { |
|
188 static GType text_overlay_halign_type = 0; |
|
189 static const GEnumValue text_overlay_halign[] = { |
|
190 {GST_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"}, |
|
191 {GST_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"}, |
|
192 {GST_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"}, |
|
193 {0, NULL, NULL}, |
|
194 }; |
|
195 |
|
196 if (!text_overlay_halign_type) { |
|
197 text_overlay_halign_type = |
|
198 g_enum_register_static ("GstTextOverlayHAlign", text_overlay_halign); |
|
199 } |
|
200 return text_overlay_halign_type; |
|
201 } |
|
202 |
|
203 |
|
204 #define GST_TYPE_TEXT_OVERLAY_WRAP_MODE (gst_text_overlay_wrap_mode_get_type()) |
|
205 static GType |
|
206 gst_text_overlay_wrap_mode_get_type (void) |
|
207 { |
|
208 static GType text_overlay_wrap_mode_type = 0; |
|
209 static const GEnumValue text_overlay_wrap_mode[] = { |
|
210 {GST_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"}, |
|
211 {GST_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"}, |
|
212 {GST_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"}, |
|
213 {GST_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"}, |
|
214 {0, NULL, NULL}, |
|
215 }; |
|
216 |
|
217 if (!text_overlay_wrap_mode_type) { |
|
218 text_overlay_wrap_mode_type = |
|
219 g_enum_register_static ("GstTextOverlayWrapMode", |
|
220 text_overlay_wrap_mode); |
|
221 } |
|
222 return text_overlay_wrap_mode_type; |
|
223 } |
|
224 |
|
225 #define GST_TYPE_TEXT_OVERLAY_LINE_ALIGN (gst_text_overlay_line_align_get_type()) |
|
226 static GType |
|
227 gst_text_overlay_line_align_get_type (void) |
|
228 { |
|
229 static GType text_overlay_line_align_type = 0; |
|
230 static const GEnumValue text_overlay_line_align[] = { |
|
231 {GST_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"}, |
|
232 {GST_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"}, |
|
233 {GST_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"}, |
|
234 {0, NULL, NULL} |
|
235 }; |
|
236 |
|
237 if (!text_overlay_line_align_type) { |
|
238 text_overlay_line_align_type = |
|
239 g_enum_register_static ("GstTextOverlayLineAlign", |
|
240 text_overlay_line_align); |
|
241 } |
|
242 return text_overlay_line_align_type; |
|
243 } |
|
244 |
|
245 /* These macros are adapted from videotestsrc.c */ |
|
246 #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width)) |
|
247 #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2) |
|
248 #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2) |
|
249 |
|
250 #define I420_Y_OFFSET(w,h) (0) |
|
251 #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h))) |
|
252 #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2)) |
|
253 |
|
254 #define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2)) |
|
255 |
|
256 #define GST_TEXT_OVERLAY_GET_COND(ov) (((GstTextOverlay *)ov)->cond) |
|
257 #define GST_TEXT_OVERLAY_WAIT(ov) (g_cond_wait (GST_TEXT_OVERLAY_GET_COND (ov), GST_OBJECT_GET_LOCK (ov))) |
|
258 #define GST_TEXT_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_TEXT_OVERLAY_GET_COND (ov))) |
|
259 #define GST_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_TEXT_OVERLAY_GET_COND (ov))) |
|
260 |
|
261 static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element, |
|
262 GstStateChange transition); |
|
263 |
|
264 static GstCaps *gst_text_overlay_getcaps (GstPad * pad); |
|
265 static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps); |
|
266 static gboolean gst_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps); |
|
267 static gboolean gst_text_overlay_src_event (GstPad * pad, GstEvent * event); |
|
268 |
|
269 static gboolean gst_text_overlay_video_event (GstPad * pad, GstEvent * event); |
|
270 static GstFlowReturn gst_text_overlay_video_chain (GstPad * pad, |
|
271 GstBuffer * buffer); |
|
272 |
|
273 static gboolean gst_text_overlay_text_event (GstPad * pad, GstEvent * event); |
|
274 static GstFlowReturn gst_text_overlay_text_chain (GstPad * pad, |
|
275 GstBuffer * buffer); |
|
276 static GstPadLinkReturn gst_text_overlay_text_pad_link (GstPad * pad, |
|
277 GstPad * peer); |
|
278 static void gst_text_overlay_text_pad_unlink (GstPad * pad); |
|
279 static void gst_text_overlay_pop_text (GstTextOverlay * overlay); |
|
280 |
|
281 static void gst_text_overlay_finalize (GObject * object); |
|
282 static void gst_text_overlay_set_property (GObject * object, guint prop_id, |
|
283 const GValue * value, GParamSpec * pspec); |
|
284 static void gst_text_overlay_get_property (GObject * object, guint prop_id, |
|
285 GValue * value, GParamSpec * pspec); |
|
286 |
|
287 GST_BOILERPLATE (GstTextOverlay, gst_text_overlay, GstElement, GST_TYPE_ELEMENT) |
|
288 |
|
289 static void gst_text_overlay_base_init (gpointer g_class) |
|
290 { |
|
291 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
|
292 |
|
293 gst_element_class_add_pad_template (element_class, |
|
294 gst_static_pad_template_get (&src_template_factory)); |
|
295 gst_element_class_add_pad_template (element_class, |
|
296 gst_static_pad_template_get (&video_sink_template_factory)); |
|
297 |
|
298 /* ugh */ |
|
299 if (!GST_IS_TIME_OVERLAY_CLASS (g_class) && |
|
300 !GST_IS_CLOCK_OVERLAY_CLASS (g_class)) { |
|
301 gst_element_class_add_pad_template (element_class, |
|
302 gst_static_pad_template_get (&text_sink_template_factory)); |
|
303 } |
|
304 |
|
305 gst_element_class_set_details (element_class, &text_overlay_details); |
|
306 } |
|
307 |
|
308 static gchar * |
|
309 gst_text_overlay_get_text (GstTextOverlay * overlay, GstBuffer * video_frame) |
|
310 { |
|
311 return g_strdup (overlay->default_text); |
|
312 } |
|
313 |
|
314 static void |
|
315 gst_text_overlay_class_init (GstTextOverlayClass * klass) |
|
316 { |
|
317 GObjectClass *gobject_class; |
|
318 GstElementClass *gstelement_class; |
|
319 |
|
320 gobject_class = (GObjectClass *) klass; |
|
321 gstelement_class = (GstElementClass *) klass; |
|
322 |
|
323 gobject_class->finalize = gst_text_overlay_finalize; |
|
324 gobject_class->set_property = gst_text_overlay_set_property; |
|
325 gobject_class->get_property = gst_text_overlay_get_property; |
|
326 |
|
327 gstelement_class->change_state = |
|
328 GST_DEBUG_FUNCPTR (gst_text_overlay_change_state); |
|
329 |
|
330 klass->get_text = gst_text_overlay_get_text; |
|
331 klass->pango_context = pango_ft2_get_context (72, 72); |
|
332 |
|
333 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT, |
|
334 g_param_spec_string ("text", "text", |
|
335 "Text to be display.", DEFAULT_PROP_TEXT, G_PARAM_READWRITE)); |
|
336 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING, |
|
337 g_param_spec_boolean ("shaded-background", "shaded background", |
|
338 "Whether to shade the background under the text area", |
|
339 DEFAULT_PROP_SHADING, G_PARAM_READWRITE)); |
|
340 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT, |
|
341 g_param_spec_enum ("valignment", "vertical alignment", |
|
342 "Vertical alignment of the text", |
|
343 GST_TYPE_TEXT_OVERLAY_VALIGN, DEFAULT_PROP_VALIGNMENT, |
|
344 G_PARAM_READWRITE)); |
|
345 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT, |
|
346 g_param_spec_enum ("halignment", "horizontal alignment", |
|
347 "Horizontal alignment of the text", GST_TYPE_TEXT_OVERLAY_HALIGN, |
|
348 DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE)); |
|
349 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGN, |
|
350 g_param_spec_string ("valign", "vertical alignment", |
|
351 "Vertical alignment of the text (deprecated; use valignment)", |
|
352 DEFAULT_PROP_VALIGN, G_PARAM_WRITABLE)); |
|
353 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGN, |
|
354 g_param_spec_string ("halign", "horizontal alignment", |
|
355 "Horizontal alignment of the text (deprecated; use halignment)", |
|
356 DEFAULT_PROP_HALIGN, G_PARAM_WRITABLE)); |
|
357 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD, |
|
358 g_param_spec_int ("xpad", "horizontal paddding", |
|
359 "Horizontal paddding when using left/right alignment", 0, G_MAXINT, |
|
360 DEFAULT_PROP_XPAD, G_PARAM_READWRITE)); |
|
361 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD, |
|
362 g_param_spec_int ("ypad", "vertical padding", |
|
363 "Vertical padding when using top/bottom alignment", 0, G_MAXINT, |
|
364 DEFAULT_PROP_YPAD, G_PARAM_READWRITE)); |
|
365 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX, |
|
366 g_param_spec_int ("deltax", "X position modifier", |
|
367 "Shift X position to the left or to the right. Unit is pixels.", |
|
368 G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX, G_PARAM_READWRITE)); |
|
369 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY, |
|
370 g_param_spec_int ("deltay", "Y position modifier", |
|
371 "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT, |
|
372 DEFAULT_PROP_DELTAY, G_PARAM_READWRITE)); |
|
373 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE, |
|
374 g_param_spec_enum ("wrap-mode", "wrap mode", |
|
375 "Whether to wrap the text and if so how.", |
|
376 GST_TYPE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE, |
|
377 G_PARAM_READWRITE)); |
|
378 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC, |
|
379 g_param_spec_string ("font-desc", "font description", |
|
380 "Pango font description of font to be used for rendering. " |
|
381 "See documentation of pango_font_description_from_string " |
|
382 "for syntax.", DEFAULT_PROP_FONT_DESC, G_PARAM_WRITABLE)); |
|
383 /** |
|
384 * GstTextOverlay:line-alignment |
|
385 * |
|
386 * Alignment of text lines relative to each other (for multi-line text) |
|
387 * |
|
388 * Since: 0.10.15 |
|
389 **/ |
|
390 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT, |
|
391 g_param_spec_enum ("line-alignment", "line alignment", |
|
392 "Alignment of text lines relative to each other.", |
|
393 GST_TYPE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT, |
|
394 G_PARAM_READWRITE)); |
|
395 /** |
|
396 * GstTextOverlay:silent |
|
397 * |
|
398 * If set, no text is rendered. Useful to switch off text rendering |
|
399 * temporarily without removing the textoverlay element from the pipeline. |
|
400 * |
|
401 * Since: 0.10.15 |
|
402 **/ |
|
403 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */ |
|
404 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT, |
|
405 g_param_spec_boolean ("silent", "silent", |
|
406 "Whether to render the text string", |
|
407 DEFAULT_PROP_SILENT, G_PARAM_READWRITE)); |
|
408 } |
|
409 |
|
410 static void |
|
411 gst_text_overlay_finalize (GObject * object) |
|
412 { |
|
413 GstTextOverlay *overlay = GST_TEXT_OVERLAY (object); |
|
414 |
|
415 g_free (overlay->default_text); |
|
416 g_free (overlay->bitmap.buffer); |
|
417 |
|
418 if (overlay->layout) { |
|
419 g_object_unref (overlay->layout); |
|
420 overlay->layout = NULL; |
|
421 } |
|
422 |
|
423 if (overlay->segment) { |
|
424 gst_segment_free (overlay->segment); |
|
425 overlay->segment = NULL; |
|
426 } |
|
427 |
|
428 if (overlay->text_buffer) { |
|
429 gst_buffer_unref (overlay->text_buffer); |
|
430 overlay->text_buffer = NULL; |
|
431 } |
|
432 |
|
433 if (overlay->cond) { |
|
434 g_cond_free (overlay->cond); |
|
435 overlay->cond = NULL; |
|
436 } |
|
437 |
|
438 G_OBJECT_CLASS (parent_class)->finalize (object); |
|
439 } |
|
440 |
|
441 static void |
|
442 gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass) |
|
443 { |
|
444 GstPadTemplate *template; |
|
445 |
|
446 /* video sink */ |
|
447 template = gst_static_pad_template_get (&video_sink_template_factory); |
|
448 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink"); |
|
449 gst_object_unref (template); |
|
450 gst_pad_set_getcaps_function (overlay->video_sinkpad, |
|
451 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps)); |
|
452 gst_pad_set_setcaps_function (overlay->video_sinkpad, |
|
453 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps)); |
|
454 gst_pad_set_event_function (overlay->video_sinkpad, |
|
455 GST_DEBUG_FUNCPTR (gst_text_overlay_video_event)); |
|
456 gst_pad_set_chain_function (overlay->video_sinkpad, |
|
457 GST_DEBUG_FUNCPTR (gst_text_overlay_video_chain)); |
|
458 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad); |
|
459 |
|
460 if (!GST_IS_TIME_OVERLAY_CLASS (klass) && !GST_IS_CLOCK_OVERLAY_CLASS (klass)) { |
|
461 /* text sink */ |
|
462 template = gst_static_pad_template_get (&text_sink_template_factory); |
|
463 overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink"); |
|
464 gst_object_unref (template); |
|
465 gst_pad_set_setcaps_function (overlay->text_sinkpad, |
|
466 GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps_txt)); |
|
467 gst_pad_set_event_function (overlay->text_sinkpad, |
|
468 GST_DEBUG_FUNCPTR (gst_text_overlay_text_event)); |
|
469 gst_pad_set_chain_function (overlay->text_sinkpad, |
|
470 GST_DEBUG_FUNCPTR (gst_text_overlay_text_chain)); |
|
471 gst_pad_set_link_function (overlay->text_sinkpad, |
|
472 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_link)); |
|
473 gst_pad_set_unlink_function (overlay->text_sinkpad, |
|
474 GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlink)); |
|
475 gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad); |
|
476 } |
|
477 |
|
478 /* (video) source */ |
|
479 template = gst_static_pad_template_get (&src_template_factory); |
|
480 overlay->srcpad = gst_pad_new_from_template (template, "src"); |
|
481 gst_object_unref (template); |
|
482 gst_pad_set_getcaps_function (overlay->srcpad, |
|
483 GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps)); |
|
484 gst_pad_set_event_function (overlay->srcpad, |
|
485 GST_DEBUG_FUNCPTR (gst_text_overlay_src_event)); |
|
486 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad); |
|
487 |
|
488 overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT; |
|
489 overlay->layout = |
|
490 pango_layout_new (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_context); |
|
491 pango_layout_set_alignment (overlay->layout, |
|
492 (PangoAlignment) overlay->line_align); |
|
493 memset (&overlay->bitmap, 0, sizeof (overlay->bitmap)); |
|
494 |
|
495 overlay->halign = DEFAULT_PROP_HALIGNMENT; |
|
496 overlay->valign = DEFAULT_PROP_VALIGNMENT; |
|
497 overlay->xpad = DEFAULT_PROP_XPAD; |
|
498 overlay->ypad = DEFAULT_PROP_YPAD; |
|
499 overlay->deltax = DEFAULT_PROP_DELTAX; |
|
500 overlay->deltay = DEFAULT_PROP_DELTAY; |
|
501 |
|
502 overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE; |
|
503 |
|
504 overlay->want_shading = DEFAULT_PROP_SHADING; |
|
505 overlay->shading_value = DEFAULT_SHADING_VALUE; |
|
506 overlay->silent = DEFAULT_PROP_SILENT; |
|
507 |
|
508 overlay->default_text = g_strdup (DEFAULT_PROP_TEXT); |
|
509 overlay->need_render = TRUE; |
|
510 |
|
511 overlay->fps_n = 0; |
|
512 overlay->fps_d = 1; |
|
513 |
|
514 overlay->text_buffer = NULL; |
|
515 overlay->text_linked = FALSE; |
|
516 overlay->video_flushing = FALSE; |
|
517 overlay->text_flushing = FALSE; |
|
518 overlay->text_eos = FALSE; |
|
519 overlay->cond = g_cond_new (); |
|
520 overlay->segment = gst_segment_new (); |
|
521 if (overlay->segment) { |
|
522 gst_segment_init (overlay->segment, GST_FORMAT_TIME); |
|
523 } else { |
|
524 GST_WARNING_OBJECT (overlay, "segment creation failed"); |
|
525 g_assert_not_reached (); |
|
526 } |
|
527 } |
|
528 |
|
529 static void |
|
530 gst_text_overlay_update_wrap_mode (GstTextOverlay * overlay) |
|
531 { |
|
532 if (overlay->wrap_mode == GST_TEXT_OVERLAY_WRAP_MODE_NONE) { |
|
533 GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE"); |
|
534 pango_layout_set_width (overlay->layout, -1); |
|
535 } else { |
|
536 GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width); |
|
537 GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode); |
|
538 pango_layout_set_width (overlay->layout, overlay->width * PANGO_SCALE); |
|
539 pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode); |
|
540 } |
|
541 } |
|
542 |
|
543 static gboolean |
|
544 gst_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps) |
|
545 { |
|
546 GstTextOverlay *overlay; |
|
547 GstStructure *structure; |
|
548 |
|
549 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); |
|
550 |
|
551 structure = gst_caps_get_structure (caps, 0); |
|
552 overlay->have_pango_markup = |
|
553 gst_structure_has_name (structure, "text/x-pango-markup"); |
|
554 |
|
555 gst_object_unref (overlay); |
|
556 |
|
557 return TRUE; |
|
558 } |
|
559 |
|
560 /* FIXME: upstream nego (e.g. when the video window is resized) */ |
|
561 |
|
562 static gboolean |
|
563 gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps) |
|
564 { |
|
565 GstTextOverlay *overlay; |
|
566 GstStructure *structure; |
|
567 gboolean ret = FALSE; |
|
568 const GValue *fps; |
|
569 |
|
570 if (!GST_PAD_IS_SINK (pad)) |
|
571 return TRUE; |
|
572 |
|
573 g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); |
|
574 |
|
575 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); |
|
576 |
|
577 overlay->width = 0; |
|
578 overlay->height = 0; |
|
579 structure = gst_caps_get_structure (caps, 0); |
|
580 fps = gst_structure_get_value (structure, "framerate"); |
|
581 |
|
582 if (gst_structure_get_int (structure, "width", &overlay->width) && |
|
583 gst_structure_get_int (structure, "height", &overlay->height) && |
|
584 fps != NULL) { |
|
585 ret = gst_pad_set_caps (overlay->srcpad, caps); |
|
586 } |
|
587 |
|
588 overlay->fps_n = gst_value_get_fraction_numerator (fps); |
|
589 overlay->fps_d = gst_value_get_fraction_denominator (fps); |
|
590 |
|
591 if (ret) { |
|
592 GST_OBJECT_LOCK (overlay); |
|
593 gst_text_overlay_update_wrap_mode (overlay); |
|
594 GST_OBJECT_UNLOCK (overlay); |
|
595 } |
|
596 |
|
597 gst_object_unref (overlay); |
|
598 |
|
599 return ret; |
|
600 } |
|
601 |
|
602 static void |
|
603 gst_text_overlay_set_property (GObject * object, guint prop_id, |
|
604 const GValue * value, GParamSpec * pspec) |
|
605 { |
|
606 GstTextOverlay *overlay = GST_TEXT_OVERLAY (object); |
|
607 |
|
608 GST_OBJECT_LOCK (overlay); |
|
609 switch (prop_id) { |
|
610 case PROP_TEXT: |
|
611 g_free (overlay->default_text); |
|
612 overlay->default_text = g_value_dup_string (value); |
|
613 overlay->need_render = TRUE; |
|
614 break; |
|
615 case PROP_SHADING: |
|
616 overlay->want_shading = g_value_get_boolean (value); |
|
617 break; |
|
618 case PROP_XPAD: |
|
619 overlay->xpad = g_value_get_int (value); |
|
620 break; |
|
621 case PROP_YPAD: |
|
622 overlay->ypad = g_value_get_int (value); |
|
623 break; |
|
624 case PROP_DELTAX: |
|
625 overlay->deltax = g_value_get_int (value); |
|
626 break; |
|
627 case PROP_DELTAY: |
|
628 overlay->deltay = g_value_get_int (value); |
|
629 break; |
|
630 case PROP_HALIGN:{ |
|
631 const gchar *s = g_value_get_string (value); |
|
632 |
|
633 if (s && g_ascii_strcasecmp (s, "left") == 0) |
|
634 overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT; |
|
635 else if (s && g_ascii_strcasecmp (s, "center") == 0) |
|
636 overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER; |
|
637 else if (s && g_ascii_strcasecmp (s, "right") == 0) |
|
638 overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT; |
|
639 else |
|
640 g_warning ("Invalid value '%s' for textoverlay property 'halign'", |
|
641 GST_STR_NULL (s)); |
|
642 break; |
|
643 } |
|
644 case PROP_VALIGN:{ |
|
645 const gchar *s = g_value_get_string (value); |
|
646 |
|
647 if (s && g_ascii_strcasecmp (s, "baseline") == 0) |
|
648 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE; |
|
649 else if (s && g_ascii_strcasecmp (s, "bottom") == 0) |
|
650 overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM; |
|
651 else if (s && g_ascii_strcasecmp (s, "top") == 0) |
|
652 overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP; |
|
653 else |
|
654 g_warning ("Invalid value '%s' for textoverlay property 'valign'", |
|
655 GST_STR_NULL (s)); |
|
656 break; |
|
657 } |
|
658 case PROP_VALIGNMENT: |
|
659 overlay->valign = g_value_get_enum (value); |
|
660 break; |
|
661 case PROP_HALIGNMENT: |
|
662 overlay->halign = g_value_get_enum (value); |
|
663 break; |
|
664 case PROP_WRAP_MODE: |
|
665 overlay->wrap_mode = g_value_get_enum (value); |
|
666 gst_text_overlay_update_wrap_mode (overlay); |
|
667 break; |
|
668 case PROP_FONT_DESC: |
|
669 { |
|
670 PangoFontDescription *desc; |
|
671 const gchar *fontdesc_str; |
|
672 |
|
673 fontdesc_str = g_value_get_string (value); |
|
674 desc = pango_font_description_from_string (fontdesc_str); |
|
675 if (desc) { |
|
676 GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str); |
|
677 pango_layout_set_font_description (overlay->layout, desc); |
|
678 pango_font_description_free (desc); |
|
679 } else { |
|
680 GST_WARNING_OBJECT (overlay, "font description parse failed: %s", |
|
681 fontdesc_str); |
|
682 } |
|
683 break; |
|
684 } |
|
685 case PROP_SILENT: |
|
686 overlay->silent = g_value_get_boolean (value); |
|
687 break; |
|
688 case PROP_LINE_ALIGNMENT: |
|
689 overlay->line_align = g_value_get_enum (value); |
|
690 pango_layout_set_alignment (overlay->layout, |
|
691 (PangoAlignment) overlay->line_align); |
|
692 break; |
|
693 default: |
|
694 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
695 break; |
|
696 } |
|
697 |
|
698 overlay->need_render = TRUE; |
|
699 GST_OBJECT_UNLOCK (overlay); |
|
700 } |
|
701 |
|
702 static void |
|
703 gst_text_overlay_get_property (GObject * object, guint prop_id, |
|
704 GValue * value, GParamSpec * pspec) |
|
705 { |
|
706 GstTextOverlay *overlay = GST_TEXT_OVERLAY (object); |
|
707 |
|
708 GST_OBJECT_LOCK (overlay); |
|
709 switch (prop_id) { |
|
710 case PROP_TEXT: |
|
711 g_value_set_string (value, overlay->default_text); |
|
712 break; |
|
713 case PROP_SHADING: |
|
714 g_value_set_boolean (value, overlay->want_shading); |
|
715 break; |
|
716 case PROP_XPAD: |
|
717 g_value_set_int (value, overlay->xpad); |
|
718 break; |
|
719 case PROP_YPAD: |
|
720 g_value_set_int (value, overlay->ypad); |
|
721 break; |
|
722 case PROP_DELTAX: |
|
723 g_value_set_int (value, overlay->deltax); |
|
724 break; |
|
725 case PROP_DELTAY: |
|
726 g_value_set_int (value, overlay->deltay); |
|
727 break; |
|
728 case PROP_VALIGNMENT: |
|
729 g_value_set_enum (value, overlay->valign); |
|
730 break; |
|
731 case PROP_HALIGNMENT: |
|
732 g_value_set_enum (value, overlay->halign); |
|
733 break; |
|
734 case PROP_WRAP_MODE: |
|
735 g_value_set_enum (value, overlay->wrap_mode); |
|
736 break; |
|
737 case PROP_SILENT: |
|
738 g_value_set_boolean (value, overlay->silent); |
|
739 break; |
|
740 case PROP_LINE_ALIGNMENT: |
|
741 g_value_set_enum (value, overlay->line_align); |
|
742 break; |
|
743 default: |
|
744 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
745 break; |
|
746 } |
|
747 |
|
748 overlay->need_render = TRUE; |
|
749 GST_OBJECT_UNLOCK (overlay); |
|
750 } |
|
751 |
|
752 static gboolean |
|
753 gst_text_overlay_src_event (GstPad * pad, GstEvent * event) |
|
754 { |
|
755 gboolean ret = FALSE; |
|
756 GstTextOverlay *overlay = NULL; |
|
757 |
|
758 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); |
|
759 |
|
760 switch (GST_EVENT_TYPE (event)) { |
|
761 case GST_EVENT_SEEK: |
|
762 /* We don't handle seek if we have not text pad */ |
|
763 if (!overlay->text_linked) { |
|
764 ret = gst_pad_push_event (overlay->video_sinkpad, event); |
|
765 goto beach; |
|
766 } |
|
767 |
|
768 GST_DEBUG_OBJECT (overlay, "seek received, driving from here"); |
|
769 |
|
770 /* Flush downstream, FIXME, only for flushing seek */ |
|
771 gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ()); |
|
772 |
|
773 /* Mark ourself as flushing, unblock chains */ |
|
774 GST_OBJECT_LOCK (overlay); |
|
775 overlay->video_flushing = TRUE; |
|
776 overlay->text_flushing = TRUE; |
|
777 gst_text_overlay_pop_text (overlay); |
|
778 GST_OBJECT_UNLOCK (overlay); |
|
779 |
|
780 /* Seek on each sink pad */ |
|
781 gst_event_ref (event); |
|
782 ret = gst_pad_push_event (overlay->video_sinkpad, event); |
|
783 if (ret) { |
|
784 ret = gst_pad_push_event (overlay->text_sinkpad, event); |
|
785 } else { |
|
786 gst_event_unref (event); |
|
787 } |
|
788 break; |
|
789 default: |
|
790 if (overlay->text_linked) { |
|
791 gst_event_ref (event); |
|
792 ret = gst_pad_push_event (overlay->video_sinkpad, event); |
|
793 gst_pad_push_event (overlay->text_sinkpad, event); |
|
794 } else { |
|
795 ret = gst_pad_push_event (overlay->video_sinkpad, event); |
|
796 } |
|
797 break; |
|
798 } |
|
799 |
|
800 beach: |
|
801 gst_object_unref (overlay); |
|
802 |
|
803 return ret; |
|
804 } |
|
805 |
|
806 static GstCaps * |
|
807 gst_text_overlay_getcaps (GstPad * pad) |
|
808 { |
|
809 GstTextOverlay *overlay; |
|
810 GstPad *otherpad; |
|
811 GstCaps *caps; |
|
812 |
|
813 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); |
|
814 |
|
815 if (pad == overlay->srcpad) |
|
816 otherpad = overlay->video_sinkpad; |
|
817 else |
|
818 otherpad = overlay->srcpad; |
|
819 |
|
820 /* we can do what the peer can */ |
|
821 caps = gst_pad_peer_get_caps (otherpad); |
|
822 if (caps) { |
|
823 GstCaps *temp; |
|
824 const GstCaps *templ; |
|
825 |
|
826 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps); |
|
827 |
|
828 /* filtered against our padtemplate */ |
|
829 templ = gst_pad_get_pad_template_caps (otherpad); |
|
830 GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ); |
|
831 temp = gst_caps_intersect (caps, templ); |
|
832 GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp); |
|
833 gst_caps_unref (caps); |
|
834 /* this is what we can do */ |
|
835 caps = temp; |
|
836 } else { |
|
837 /* no peer, our padtemplate is enough then */ |
|
838 caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); |
|
839 } |
|
840 |
|
841 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps); |
|
842 |
|
843 gst_object_unref (overlay); |
|
844 |
|
845 return caps; |
|
846 } |
|
847 |
|
848 #define BOX_XPAD 6 |
|
849 #define BOX_YPAD 6 |
|
850 |
|
851 static inline void |
|
852 gst_text_overlay_shade_y (GstTextOverlay * overlay, guchar * dest, |
|
853 guint dest_stride, gint x0, gint x1, gint y0, gint y1) |
|
854 { |
|
855 gint i, j; |
|
856 |
|
857 x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width); |
|
858 x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width); |
|
859 |
|
860 y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height); |
|
861 y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height); |
|
862 |
|
863 for (i = y0; i < y1; ++i) { |
|
864 for (j = x0; j < x1; ++j) { |
|
865 gint y = dest[(i * dest_stride) + j] + overlay->shading_value; |
|
866 |
|
867 dest[(i * dest_stride) + j] = CLAMP (y, 0, 255); |
|
868 } |
|
869 } |
|
870 } |
|
871 |
|
872 /* FIXME: |
|
873 * - use proper strides and offset for I420 |
|
874 * - don't draw over the edge of the picture (try a longer |
|
875 * text with a huge font size) |
|
876 */ |
|
877 |
|
878 static inline void |
|
879 gst_text_overlay_blit_yuv420 (GstTextOverlay * overlay, FT_Bitmap * bitmap, |
|
880 guint8 * yuv_pixels, gint x0, gint y0) |
|
881 { |
|
882 int y; /* text bitmap coordinates */ |
|
883 int x1, y1; /* video buffer coordinates */ |
|
884 int bit_rowinc, uv_rowinc; |
|
885 guint8 *p, *bitp, *u_p; |
|
886 int video_width, video_height; |
|
887 int bitmap_x0 = 0; //x0 < 1 ? -(x0 - 1) : 1; /* 1 pixel border */ |
|
888 int bitmap_y0 = y0 < 1 ? -(y0 - 1) : 1; /* 1 pixel border */ |
|
889 int bitmap_width = bitmap->width - bitmap_x0; |
|
890 int bitmap_height = bitmap->rows - bitmap_y0; |
|
891 int u_plane_size; |
|
892 int skip_y, skip_x; |
|
893 guint8 v; |
|
894 |
|
895 video_width = I420_Y_ROWSTRIDE (overlay->width); |
|
896 video_height = overlay->height; |
|
897 |
|
898 /* |
|
899 if (x0 < 0 && abs (x0) < bitmap_width) { |
|
900 bitmap_x0 = abs (x0); |
|
901 x0 = 0; |
|
902 } |
|
903 */ |
|
904 |
|
905 if (x0 + bitmap_x0 + bitmap_width > overlay->width - 1) /* 1 pixel border */ |
|
906 bitmap_width -= x0 + bitmap_x0 + bitmap_width - overlay->width + 1; |
|
907 if (y0 + bitmap_y0 + bitmap_height > video_height - 1) /* 1 pixel border */ |
|
908 bitmap_height -= y0 + bitmap_y0 + bitmap_height - video_height + 1; |
|
909 |
|
910 uv_rowinc = video_width / 2 - bitmap_width / 2; |
|
911 bit_rowinc = bitmap->pitch - bitmap_width; |
|
912 u_plane_size = (video_width / 2) * (video_height / 2); |
|
913 |
|
914 y1 = y0 + bitmap_y0; |
|
915 x1 = x0 + bitmap_x0; |
|
916 bitp = bitmap->buffer + bitmap->pitch * bitmap_y0 + bitmap_x0; |
|
917 for (y = bitmap_y0; y < bitmap_y0 + bitmap_height; y++) { |
|
918 int n; |
|
919 |
|
920 p = yuv_pixels + (y + y0) * I420_Y_ROWSTRIDE (overlay->width) + x1; |
|
921 for (n = bitmap_width; n > 0; --n) { |
|
922 v = *bitp; |
|
923 if (v) { |
|
924 p[-1] = CLAMP (p[-1] - v, 0, 255); |
|
925 p[1] = CLAMP (p[1] - v, 0, 255); |
|
926 p[-video_width] = CLAMP (p[-video_width] - v, 0, 255); |
|
927 p[video_width] = CLAMP (p[video_width] - v, 0, 255); |
|
928 } |
|
929 p++; |
|
930 bitp++; |
|
931 } |
|
932 bitp += bit_rowinc; |
|
933 } |
|
934 |
|
935 y = bitmap_y0; |
|
936 y1 = y0 + bitmap_y0; |
|
937 x1 = x0 + bitmap_x0; |
|
938 bitp = bitmap->buffer + bitmap->pitch * bitmap_y0 + bitmap_x0; |
|
939 p = yuv_pixels + video_width * y1 + x1; |
|
940 u_p = |
|
941 yuv_pixels + video_width * video_height + (video_width >> 1) * (y1 >> 1) + |
|
942 (x1 >> 1); |
|
943 skip_y = 0; |
|
944 skip_x = 0; |
|
945 |
|
946 for (; y < bitmap_y0 + bitmap_height; y++) { |
|
947 int n; |
|
948 |
|
949 x1 = x0 + bitmap_x0; |
|
950 skip_x = 0; |
|
951 for (n = bitmap_width; n > 0; --n) { |
|
952 v = *bitp; |
|
953 if (v) { |
|
954 *p = v; |
|
955 if (!skip_y) { |
|
956 u_p[0] = u_p[u_plane_size] = 0x80; |
|
957 } |
|
958 } |
|
959 if (!skip_y) { |
|
960 skip_x = !skip_x; |
|
961 if (!skip_x) |
|
962 u_p++; |
|
963 } |
|
964 p++; |
|
965 bitp++; |
|
966 } |
|
967 /*if (!skip_x && !skip_y) u_p--; */ |
|
968 p += I420_Y_ROWSTRIDE (overlay->width) - bitmap_width; |
|
969 bitp += bit_rowinc; |
|
970 skip_y = !skip_y; |
|
971 u_p += skip_y ? uv_rowinc : 0; |
|
972 } |
|
973 } |
|
974 |
|
975 static void |
|
976 gst_text_overlay_resize_bitmap (GstTextOverlay * overlay, gint width, |
|
977 gint height) |
|
978 { |
|
979 FT_Bitmap *bitmap = &overlay->bitmap; |
|
980 int pitch = (width | 3) + 1; |
|
981 int size = pitch * height; |
|
982 |
|
983 /* no need to keep reallocating; just keep the maximum size so far */ |
|
984 if (size <= overlay->bitmap_buffer_size) { |
|
985 bitmap->rows = height; |
|
986 bitmap->width = width; |
|
987 bitmap->pitch = pitch; |
|
988 memset (bitmap->buffer, 0, overlay->bitmap_buffer_size); |
|
989 return; |
|
990 } |
|
991 if (!bitmap->buffer) { |
|
992 /* initialize */ |
|
993 bitmap->pixel_mode = ft_pixel_mode_grays; |
|
994 bitmap->num_grays = 256; |
|
995 } |
|
996 overlay->bitmap_buffer_size = size; |
|
997 bitmap->buffer = g_realloc (bitmap->buffer, size); |
|
998 memset (bitmap->buffer, 0, size); |
|
999 bitmap->rows = height; |
|
1000 bitmap->width = width; |
|
1001 bitmap->pitch = pitch; |
|
1002 } |
|
1003 |
|
1004 static void |
|
1005 gst_text_overlay_render_text (GstTextOverlay * overlay, |
|
1006 const gchar * text, gint textlen) |
|
1007 { |
|
1008 PangoRectangle ink_rect, logical_rect; |
|
1009 gchar *string; |
|
1010 |
|
1011 if (!overlay->need_render) { |
|
1012 GST_DEBUG ("Using previously rendered text."); |
|
1013 return; |
|
1014 } |
|
1015 |
|
1016 /* -1 is the whole string */ |
|
1017 if (text != NULL && textlen < 0) { |
|
1018 textlen = strlen (text); |
|
1019 } |
|
1020 |
|
1021 if (text != NULL) { |
|
1022 string = g_strndup (text, textlen); |
|
1023 } else { /* empty string */ |
|
1024 string = g_strdup (" "); |
|
1025 } |
|
1026 g_strdelimit (string, "\r\t", ' '); |
|
1027 textlen = strlen (string); |
|
1028 |
|
1029 /* FIXME: should we check for UTF-8 here? */ |
|
1030 |
|
1031 GST_DEBUG ("Rendering '%s'", string); |
|
1032 pango_layout_set_markup (overlay->layout, string, textlen); |
|
1033 |
|
1034 pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect); |
|
1035 gst_text_overlay_resize_bitmap (overlay, ink_rect.width, |
|
1036 ink_rect.height + ink_rect.y); |
|
1037 pango_ft2_render_layout (&overlay->bitmap, overlay->layout, -ink_rect.x, 0); |
|
1038 overlay->baseline_y = ink_rect.y; |
|
1039 |
|
1040 g_free (string); |
|
1041 |
|
1042 overlay->need_render = FALSE; |
|
1043 } |
|
1044 |
|
1045 static GstFlowReturn |
|
1046 gst_text_overlay_push_frame (GstTextOverlay * overlay, GstBuffer * video_frame) |
|
1047 { |
|
1048 gint xpos, ypos; |
|
1049 |
|
1050 video_frame = gst_buffer_make_writable (video_frame); |
|
1051 |
|
1052 switch (overlay->halign) { |
|
1053 case GST_TEXT_OVERLAY_HALIGN_LEFT: |
|
1054 xpos = overlay->xpad; |
|
1055 break; |
|
1056 case GST_TEXT_OVERLAY_HALIGN_CENTER: |
|
1057 xpos = (overlay->width - overlay->bitmap.width) / 2; |
|
1058 break; |
|
1059 case GST_TEXT_OVERLAY_HALIGN_RIGHT: |
|
1060 xpos = overlay->width - overlay->bitmap.width - overlay->xpad; |
|
1061 break; |
|
1062 default: |
|
1063 xpos = 0; |
|
1064 } |
|
1065 xpos += overlay->deltax; |
|
1066 |
|
1067 |
|
1068 switch (overlay->valign) { |
|
1069 case GST_TEXT_OVERLAY_VALIGN_BOTTOM: |
|
1070 ypos = overlay->height - overlay->bitmap.rows - overlay->ypad; |
|
1071 break; |
|
1072 case GST_TEXT_OVERLAY_VALIGN_BASELINE: |
|
1073 ypos = overlay->height - (overlay->bitmap.rows + overlay->ypad); |
|
1074 break; |
|
1075 case GST_TEXT_OVERLAY_VALIGN_TOP: |
|
1076 ypos = overlay->ypad; |
|
1077 break; |
|
1078 default: |
|
1079 ypos = overlay->ypad; |
|
1080 break; |
|
1081 } |
|
1082 ypos += overlay->deltay; |
|
1083 |
|
1084 /* shaded background box */ |
|
1085 if (overlay->want_shading) { |
|
1086 gst_text_overlay_shade_y (overlay, |
|
1087 GST_BUFFER_DATA (video_frame), |
|
1088 I420_Y_ROWSTRIDE (overlay->width), |
|
1089 xpos, xpos + overlay->bitmap.width, ypos, ypos + overlay->bitmap.rows); |
|
1090 } |
|
1091 |
|
1092 |
|
1093 if (overlay->bitmap.buffer) { |
|
1094 gst_text_overlay_blit_yuv420 (overlay, &overlay->bitmap, |
|
1095 GST_BUFFER_DATA (video_frame), xpos, ypos); |
|
1096 } |
|
1097 |
|
1098 return gst_pad_push (overlay->srcpad, video_frame); |
|
1099 } |
|
1100 |
|
1101 static GstPadLinkReturn |
|
1102 gst_text_overlay_text_pad_link (GstPad * pad, GstPad * peer) |
|
1103 { |
|
1104 GstTextOverlay *overlay; |
|
1105 |
|
1106 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); |
|
1107 |
|
1108 GST_DEBUG_OBJECT (overlay, "Text pad linked"); |
|
1109 |
|
1110 overlay->text_linked = TRUE; |
|
1111 |
|
1112 gst_object_unref (overlay); |
|
1113 |
|
1114 return GST_PAD_LINK_OK; |
|
1115 } |
|
1116 |
|
1117 static void |
|
1118 gst_text_overlay_text_pad_unlink (GstPad * pad) |
|
1119 { |
|
1120 GstTextOverlay *overlay; |
|
1121 |
|
1122 /* don't use gst_pad_get_parent() here, will deadlock */ |
|
1123 overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad)); |
|
1124 |
|
1125 GST_DEBUG_OBJECT (overlay, "Text pad unlinked"); |
|
1126 |
|
1127 overlay->text_linked = FALSE; |
|
1128 |
|
1129 gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED); |
|
1130 } |
|
1131 |
|
1132 static gboolean |
|
1133 gst_text_overlay_text_event (GstPad * pad, GstEvent * event) |
|
1134 { |
|
1135 gboolean ret = FALSE; |
|
1136 GstTextOverlay *overlay = NULL; |
|
1137 |
|
1138 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); |
|
1139 |
|
1140 GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); |
|
1141 |
|
1142 switch (GST_EVENT_TYPE (event)) { |
|
1143 case GST_EVENT_NEWSEGMENT:{ |
|
1144 GstFormat fmt; |
|
1145 gboolean update; |
|
1146 gdouble rate, applied_rate; |
|
1147 gint64 cur, stop, time; |
|
1148 |
|
1149 overlay->text_eos = FALSE; |
|
1150 |
|
1151 gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, |
|
1152 &fmt, &cur, &stop, &time); |
|
1153 |
|
1154 if (fmt == GST_FORMAT_TIME) { |
|
1155 GST_OBJECT_LOCK (overlay); |
|
1156 gst_segment_set_newsegment_full (&overlay->text_segment, update, rate, |
|
1157 applied_rate, GST_FORMAT_TIME, cur, stop, time); |
|
1158 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT, |
|
1159 &overlay->text_segment); |
|
1160 GST_OBJECT_UNLOCK (overlay); |
|
1161 } else { |
|
1162 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL), |
|
1163 ("received non-TIME newsegment event on text input")); |
|
1164 } |
|
1165 |
|
1166 gst_event_unref (event); |
|
1167 ret = TRUE; |
|
1168 |
|
1169 /* wake up the video chain, it might be waiting for a text buffer or |
|
1170 * a text segment update */ |
|
1171 GST_OBJECT_LOCK (overlay); |
|
1172 GST_TEXT_OVERLAY_BROADCAST (overlay); |
|
1173 GST_OBJECT_UNLOCK (overlay); |
|
1174 break; |
|
1175 } |
|
1176 case GST_EVENT_FLUSH_STOP: |
|
1177 GST_OBJECT_LOCK (overlay); |
|
1178 overlay->text_flushing = FALSE; |
|
1179 gst_text_overlay_pop_text (overlay); |
|
1180 GST_OBJECT_UNLOCK (overlay); |
|
1181 gst_event_unref (event); |
|
1182 ret = TRUE; |
|
1183 break; |
|
1184 case GST_EVENT_FLUSH_START: |
|
1185 GST_OBJECT_LOCK (overlay); |
|
1186 overlay->text_flushing = TRUE; |
|
1187 GST_TEXT_OVERLAY_BROADCAST (overlay); |
|
1188 GST_OBJECT_UNLOCK (overlay); |
|
1189 gst_event_unref (event); |
|
1190 ret = TRUE; |
|
1191 break; |
|
1192 case GST_EVENT_EOS: |
|
1193 GST_OBJECT_LOCK (overlay); |
|
1194 overlay->text_flushing = TRUE; |
|
1195 overlay->text_eos = TRUE; |
|
1196 GST_INFO_OBJECT (overlay, "EOS"); |
|
1197 /* wake up the video chain, it might be waiting for a text buffer or |
|
1198 * a text segment update */ |
|
1199 GST_TEXT_OVERLAY_BROADCAST (overlay); |
|
1200 GST_OBJECT_UNLOCK (overlay); |
|
1201 gst_event_unref (event); |
|
1202 ret = TRUE; |
|
1203 break; |
|
1204 default: |
|
1205 ret = gst_pad_event_default (pad, event); |
|
1206 break; |
|
1207 } |
|
1208 |
|
1209 gst_object_unref (overlay); |
|
1210 |
|
1211 return ret; |
|
1212 } |
|
1213 |
|
1214 static gboolean |
|
1215 gst_text_overlay_video_event (GstPad * pad, GstEvent * event) |
|
1216 { |
|
1217 gboolean ret = FALSE; |
|
1218 GstTextOverlay *overlay = NULL; |
|
1219 |
|
1220 overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); |
|
1221 |
|
1222 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); |
|
1223 |
|
1224 switch (GST_EVENT_TYPE (event)) { |
|
1225 case GST_EVENT_NEWSEGMENT: |
|
1226 { |
|
1227 GstFormat format; |
|
1228 gdouble rate; |
|
1229 gint64 start, stop, time; |
|
1230 gboolean update; |
|
1231 |
|
1232 GST_DEBUG_OBJECT (overlay, "received new segment"); |
|
1233 |
|
1234 gst_event_parse_new_segment (event, &update, &rate, &format, &start, |
|
1235 &stop, &time); |
|
1236 |
|
1237 if (format == GST_FORMAT_TIME) { |
|
1238 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT, |
|
1239 overlay->segment); |
|
1240 |
|
1241 gst_segment_set_newsegment (overlay->segment, update, rate, format, |
|
1242 start, stop, time); |
|
1243 } else { |
|
1244 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL), |
|
1245 ("received non-TIME newsegment event on video input")); |
|
1246 } |
|
1247 |
|
1248 ret = gst_pad_event_default (pad, event); |
|
1249 break; |
|
1250 } |
|
1251 case GST_EVENT_EOS: |
|
1252 GST_OBJECT_LOCK (overlay); |
|
1253 overlay->video_flushing = TRUE; |
|
1254 overlay->text_flushing = TRUE; |
|
1255 gst_text_overlay_pop_text (overlay); |
|
1256 GST_OBJECT_UNLOCK (overlay); |
|
1257 ret = gst_pad_event_default (pad, event); |
|
1258 break; |
|
1259 case GST_EVENT_FLUSH_START: |
|
1260 GST_OBJECT_LOCK (overlay); |
|
1261 overlay->video_flushing = TRUE; |
|
1262 GST_TEXT_OVERLAY_BROADCAST (overlay); |
|
1263 GST_OBJECT_UNLOCK (overlay); |
|
1264 ret = gst_pad_event_default (pad, event); |
|
1265 break; |
|
1266 case GST_EVENT_FLUSH_STOP: |
|
1267 GST_OBJECT_LOCK (overlay); |
|
1268 overlay->video_flushing = FALSE; |
|
1269 GST_OBJECT_UNLOCK (overlay); |
|
1270 ret = gst_pad_event_default (pad, event); |
|
1271 break; |
|
1272 default: |
|
1273 ret = gst_pad_event_default (pad, event); |
|
1274 break; |
|
1275 } |
|
1276 |
|
1277 gst_object_unref (overlay); |
|
1278 |
|
1279 return ret; |
|
1280 } |
|
1281 |
|
1282 /* Called with lock held */ |
|
1283 static void |
|
1284 gst_text_overlay_pop_text (GstTextOverlay * overlay) |
|
1285 { |
|
1286 g_return_if_fail (GST_IS_TEXT_OVERLAY (overlay)); |
|
1287 |
|
1288 if (overlay->text_buffer) { |
|
1289 /* update text_segment's last stop */ |
|
1290 if (overlay->text_segment.format == GST_FORMAT_TIME && |
|
1291 GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer)) { |
|
1292 overlay->text_segment.last_stop = |
|
1293 GST_BUFFER_TIMESTAMP (overlay->text_buffer); |
|
1294 if (GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) { |
|
1295 overlay->text_segment.last_stop += |
|
1296 GST_BUFFER_DURATION (overlay->text_buffer); |
|
1297 } |
|
1298 } |
|
1299 GST_DEBUG_OBJECT (overlay, "releasing text buffer %p", |
|
1300 overlay->text_buffer); |
|
1301 gst_buffer_unref (overlay->text_buffer); |
|
1302 overlay->text_buffer = NULL; |
|
1303 } |
|
1304 |
|
1305 /* Let the text task know we used that buffer */ |
|
1306 GST_TEXT_OVERLAY_BROADCAST (overlay); |
|
1307 } |
|
1308 |
|
1309 /* We receive text buffers here. If they are out of segment we just ignore them. |
|
1310 If the buffer is in our segment we keep it internally except if another one |
|
1311 is already waiting here, in that case we wait that it gets kicked out */ |
|
1312 static GstFlowReturn |
|
1313 gst_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer) |
|
1314 { |
|
1315 GstFlowReturn ret = GST_FLOW_OK; |
|
1316 GstTextOverlay *overlay = NULL; |
|
1317 gboolean in_seg = FALSE; |
|
1318 gint64 clip_start = 0, clip_stop = 0; |
|
1319 |
|
1320 overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad)); |
|
1321 |
|
1322 GST_OBJECT_LOCK (overlay); |
|
1323 |
|
1324 if (overlay->text_eos) { |
|
1325 GST_OBJECT_UNLOCK (overlay); |
|
1326 ret = GST_FLOW_UNEXPECTED; |
|
1327 GST_LOG_OBJECT (overlay, "text EOS"); |
|
1328 goto beach; |
|
1329 } |
|
1330 |
|
1331 if (overlay->text_flushing) { |
|
1332 GST_OBJECT_UNLOCK (overlay); |
|
1333 ret = GST_FLOW_WRONG_STATE; |
|
1334 GST_LOG_OBJECT (overlay, "text flushing"); |
|
1335 goto beach; |
|
1336 } |
|
1337 |
|
1338 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%" |
|
1339 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, overlay->segment, |
|
1340 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), |
|
1341 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) + |
|
1342 GST_BUFFER_DURATION (buffer))); |
|
1343 |
|
1344 in_seg = gst_segment_clip (overlay->segment, GST_FORMAT_TIME, |
|
1345 GST_BUFFER_TIMESTAMP (buffer), |
|
1346 GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer), |
|
1347 &clip_start, &clip_stop); |
|
1348 |
|
1349 if (in_seg) { |
|
1350 GST_BUFFER_TIMESTAMP (buffer) = clip_start; |
|
1351 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start; |
|
1352 |
|
1353 /* Wait for the previous buffer to go away */ |
|
1354 while (overlay->text_buffer != NULL) { |
|
1355 GST_DEBUG ("Pad %s:%s has a buffer queued, waiting", |
|
1356 GST_DEBUG_PAD_NAME (pad)); |
|
1357 GST_TEXT_OVERLAY_WAIT (overlay); |
|
1358 GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad)); |
|
1359 if (overlay->text_flushing) { |
|
1360 GST_OBJECT_UNLOCK (overlay); |
|
1361 ret = GST_FLOW_WRONG_STATE; |
|
1362 goto beach; |
|
1363 } |
|
1364 } |
|
1365 |
|
1366 overlay->text_buffer = buffer; |
|
1367 /* That's a new text buffer we need to render */ |
|
1368 overlay->need_render = TRUE; |
|
1369 |
|
1370 /* in case the video chain is waiting for a text buffer, wake it up */ |
|
1371 GST_TEXT_OVERLAY_BROADCAST (overlay); |
|
1372 } |
|
1373 |
|
1374 GST_OBJECT_UNLOCK (overlay); |
|
1375 |
|
1376 beach: |
|
1377 |
|
1378 return ret; |
|
1379 } |
|
1380 |
|
1381 static GstFlowReturn |
|
1382 gst_text_overlay_video_chain (GstPad * pad, GstBuffer * buffer) |
|
1383 { |
|
1384 GstTextOverlayClass *klass; |
|
1385 GstTextOverlay *overlay; |
|
1386 GstFlowReturn ret = GST_FLOW_OK; |
|
1387 gboolean in_seg = FALSE; |
|
1388 gint64 start, stop, clip_start = 0, clip_stop = 0; |
|
1389 gchar *text = NULL; |
|
1390 |
|
1391 overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad)); |
|
1392 klass = GST_TEXT_OVERLAY_GET_CLASS (overlay); |
|
1393 |
|
1394 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) |
|
1395 goto missing_timestamp; |
|
1396 |
|
1397 /* ignore buffers that are outside of the current segment */ |
|
1398 start = GST_BUFFER_TIMESTAMP (buffer); |
|
1399 |
|
1400 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) { |
|
1401 stop = GST_CLOCK_TIME_NONE; |
|
1402 } else { |
|
1403 stop = start + GST_BUFFER_DURATION (buffer); |
|
1404 } |
|
1405 |
|
1406 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%" |
|
1407 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, overlay->segment, |
|
1408 GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); |
|
1409 |
|
1410 /* segment_clip() will adjust start unconditionally to segment_start if |
|
1411 * no stop time is provided, so handle this ourselves */ |
|
1412 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment->start) |
|
1413 goto out_of_segment; |
|
1414 |
|
1415 in_seg = gst_segment_clip (overlay->segment, GST_FORMAT_TIME, start, stop, |
|
1416 &clip_start, &clip_stop); |
|
1417 |
|
1418 if (!in_seg) |
|
1419 goto out_of_segment; |
|
1420 |
|
1421 /* if the buffer is only partially in the segment, fix up stamps */ |
|
1422 if (clip_start != start || (stop != -1 && clip_stop != stop)) { |
|
1423 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment"); |
|
1424 buffer = gst_buffer_make_metadata_writable (buffer); |
|
1425 GST_BUFFER_TIMESTAMP (buffer) = clip_start; |
|
1426 if (stop != -1) |
|
1427 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start; |
|
1428 } |
|
1429 |
|
1430 /* now, after we've done the clipping, fix up end time if there's no |
|
1431 * duration (we only use those estimated values internally though, we |
|
1432 * don't want to set bogus values on the buffer itself) */ |
|
1433 if (stop == -1) { |
|
1434 GstStructure *s; |
|
1435 gint fps_num, fps_denom; |
|
1436 |
|
1437 s = gst_caps_get_structure (GST_PAD_CAPS (pad), 0); |
|
1438 if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom)) { |
|
1439 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate"); |
|
1440 stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num); |
|
1441 } else { |
|
1442 GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration"); |
|
1443 stop = start + 1; /* we need to assume some interval */ |
|
1444 } |
|
1445 } |
|
1446 |
|
1447 wait_for_text_buf: |
|
1448 |
|
1449 GST_OBJECT_LOCK (overlay); |
|
1450 |
|
1451 if (overlay->video_flushing) |
|
1452 goto flushing; |
|
1453 |
|
1454 if (overlay->silent) { |
|
1455 GST_OBJECT_UNLOCK (overlay); |
|
1456 ret = gst_pad_push (overlay->srcpad, buffer); |
|
1457 |
|
1458 /* Update last_stop */ |
|
1459 gst_segment_set_last_stop (overlay->segment, GST_FORMAT_TIME, clip_start); |
|
1460 |
|
1461 return ret; |
|
1462 } |
|
1463 |
|
1464 /* Text pad not linked, rendering internal text */ |
|
1465 if (!overlay->text_linked) { |
|
1466 if (klass->get_text) { |
|
1467 text = klass->get_text (overlay, buffer); |
|
1468 } else { |
|
1469 text = g_strdup (overlay->default_text); |
|
1470 } |
|
1471 |
|
1472 GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default " |
|
1473 "text: '%s'", GST_STR_NULL (text)); |
|
1474 |
|
1475 GST_OBJECT_UNLOCK (overlay); |
|
1476 |
|
1477 if (text != NULL && *text != '\0') { |
|
1478 /* Render and push */ |
|
1479 gst_text_overlay_render_text (overlay, text, -1); |
|
1480 ret = gst_text_overlay_push_frame (overlay, buffer); |
|
1481 } else { |
|
1482 /* Invalid or empty string */ |
|
1483 ret = gst_pad_push (overlay->srcpad, buffer); |
|
1484 } |
|
1485 } else { |
|
1486 /* Text pad linked, check if we have a text buffer queued */ |
|
1487 if (overlay->text_buffer) { |
|
1488 gboolean pop_text = FALSE; |
|
1489 gint64 text_start, text_end; |
|
1490 |
|
1491 /* if the text buffer isn't stamped right, pop it off the |
|
1492 * queue and display it for the current video frame only */ |
|
1493 if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) || |
|
1494 !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) { |
|
1495 GST_WARNING_OBJECT (overlay, |
|
1496 "Got text buffer with invalid timestamp or duration"); |
|
1497 text_start = start; |
|
1498 text_end = stop; |
|
1499 pop_text = TRUE; |
|
1500 } else { |
|
1501 text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer); |
|
1502 text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer); |
|
1503 } |
|
1504 |
|
1505 GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, |
|
1506 GST_TIME_ARGS (text_start), GST_TIME_ARGS (text_end)); |
|
1507 GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, |
|
1508 GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); |
|
1509 |
|
1510 /* Text too old or in the future */ |
|
1511 if (text_end <= start) { |
|
1512 /* text buffer too old, get rid of it and do nothing */ |
|
1513 GST_LOG_OBJECT (overlay, "text buffer too old, popping"); |
|
1514 pop_text = FALSE; |
|
1515 gst_text_overlay_pop_text (overlay); |
|
1516 GST_OBJECT_UNLOCK (overlay); |
|
1517 goto wait_for_text_buf; |
|
1518 } else if (stop <= text_start) { |
|
1519 GST_LOG_OBJECT (overlay, "text in future, pushing video buf"); |
|
1520 GST_OBJECT_UNLOCK (overlay); |
|
1521 /* Push the video frame */ |
|
1522 ret = gst_pad_push (overlay->srcpad, buffer); |
|
1523 } else { |
|
1524 gchar *in_text; |
|
1525 gsize in_size; |
|
1526 |
|
1527 in_text = (gchar *) GST_BUFFER_DATA (overlay->text_buffer); |
|
1528 in_size = GST_BUFFER_SIZE (overlay->text_buffer); |
|
1529 |
|
1530 /* g_markup_escape_text() absolutely requires valid UTF8 input, it |
|
1531 * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING |
|
1532 * here on purpose, this is something that needs fixing upstream */ |
|
1533 if (!g_utf8_validate (in_text, in_size, NULL)) { |
|
1534 const gchar *end = NULL; |
|
1535 |
|
1536 GST_WARNING_OBJECT (overlay, "received invalid UTF-8"); |
|
1537 in_text = g_strndup (in_text, in_size); |
|
1538 while (!g_utf8_validate (in_text, in_size, &end) && end) |
|
1539 *((gchar *) end) = '*'; |
|
1540 } |
|
1541 |
|
1542 /* Get the string */ |
|
1543 if (overlay->have_pango_markup) { |
|
1544 text = g_strndup (in_text, in_size); |
|
1545 } else { |
|
1546 text = g_markup_escape_text (in_text, in_size); |
|
1547 } |
|
1548 |
|
1549 if (text != NULL && *text != '\0') { |
|
1550 gint text_len = strlen (text); |
|
1551 |
|
1552 while (text_len > 0 && (text[text_len - 1] == '\n' || |
|
1553 text[text_len - 1] == '\r')) { |
|
1554 --text_len; |
|
1555 } |
|
1556 GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text); |
|
1557 gst_text_overlay_render_text (overlay, text, text_len); |
|
1558 } else { |
|
1559 GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)"); |
|
1560 gst_text_overlay_render_text (overlay, " ", 1); |
|
1561 } |
|
1562 |
|
1563 if (in_text != (gchar *) GST_BUFFER_DATA (overlay->text_buffer)) |
|
1564 g_free (in_text); |
|
1565 |
|
1566 GST_OBJECT_UNLOCK (overlay); |
|
1567 ret = gst_text_overlay_push_frame (overlay, buffer); |
|
1568 |
|
1569 if (text_end <= stop) { |
|
1570 GST_LOG_OBJECT (overlay, "text buffer not needed any longer"); |
|
1571 pop_text = TRUE; |
|
1572 } |
|
1573 } |
|
1574 if (pop_text) { |
|
1575 GST_OBJECT_LOCK (overlay); |
|
1576 gst_text_overlay_pop_text (overlay); |
|
1577 GST_OBJECT_UNLOCK (overlay); |
|
1578 } |
|
1579 } else { |
|
1580 gboolean wait_for_text_buf = TRUE; |
|
1581 |
|
1582 if (overlay->text_eos) |
|
1583 wait_for_text_buf = FALSE; |
|
1584 |
|
1585 /* Text pad linked, but no text buffer available - what now? */ |
|
1586 if (overlay->text_segment.format == GST_FORMAT_TIME) { |
|
1587 if (GST_BUFFER_TIMESTAMP (buffer) < overlay->text_segment.start || |
|
1588 GST_BUFFER_TIMESTAMP (buffer) < overlay->text_segment.last_stop) { |
|
1589 wait_for_text_buf = FALSE; |
|
1590 } |
|
1591 } |
|
1592 |
|
1593 if (wait_for_text_buf) { |
|
1594 GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one"); |
|
1595 GST_TEXT_OVERLAY_WAIT (overlay); |
|
1596 GST_DEBUG_OBJECT (overlay, "resuming"); |
|
1597 GST_OBJECT_UNLOCK (overlay); |
|
1598 goto wait_for_text_buf; |
|
1599 } else { |
|
1600 GST_OBJECT_UNLOCK (overlay); |
|
1601 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer"); |
|
1602 ret = gst_pad_push (overlay->srcpad, buffer); |
|
1603 } |
|
1604 } |
|
1605 } |
|
1606 |
|
1607 g_free (text); |
|
1608 |
|
1609 /* Update last_stop */ |
|
1610 gst_segment_set_last_stop (overlay->segment, GST_FORMAT_TIME, clip_start); |
|
1611 |
|
1612 return ret; |
|
1613 |
|
1614 missing_timestamp: |
|
1615 { |
|
1616 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding"); |
|
1617 gst_buffer_unref (buffer); |
|
1618 return GST_FLOW_OK; |
|
1619 } |
|
1620 |
|
1621 flushing: |
|
1622 { |
|
1623 GST_OBJECT_UNLOCK (overlay); |
|
1624 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer"); |
|
1625 gst_buffer_unref (buffer); |
|
1626 return GST_FLOW_WRONG_STATE; |
|
1627 } |
|
1628 |
|
1629 out_of_segment: |
|
1630 { |
|
1631 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding"); |
|
1632 gst_buffer_unref (buffer); |
|
1633 return GST_FLOW_OK; |
|
1634 } |
|
1635 } |
|
1636 |
|
1637 static GstStateChangeReturn |
|
1638 gst_text_overlay_change_state (GstElement * element, GstStateChange transition) |
|
1639 { |
|
1640 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; |
|
1641 GstTextOverlay *overlay = GST_TEXT_OVERLAY (element); |
|
1642 |
|
1643 switch (transition) { |
|
1644 case GST_STATE_CHANGE_PAUSED_TO_READY: |
|
1645 GST_OBJECT_LOCK (overlay); |
|
1646 overlay->text_flushing = TRUE; |
|
1647 overlay->video_flushing = TRUE; |
|
1648 /* pop_text will broadcast on the GCond and thus also make the video |
|
1649 * chain exit if it's waiting for a text buffer */ |
|
1650 gst_text_overlay_pop_text (overlay); |
|
1651 GST_OBJECT_UNLOCK (overlay); |
|
1652 break; |
|
1653 default: |
|
1654 break; |
|
1655 } |
|
1656 |
|
1657 ret = parent_class->change_state (element, transition); |
|
1658 if (ret == GST_STATE_CHANGE_FAILURE) |
|
1659 return ret; |
|
1660 |
|
1661 switch (transition) { |
|
1662 case GST_STATE_CHANGE_READY_TO_PAUSED: |
|
1663 GST_OBJECT_LOCK (overlay); |
|
1664 overlay->text_flushing = FALSE; |
|
1665 overlay->video_flushing = FALSE; |
|
1666 GST_OBJECT_UNLOCK (overlay); |
|
1667 break; |
|
1668 default: |
|
1669 break; |
|
1670 } |
|
1671 |
|
1672 return ret; |
|
1673 } |
|
1674 |
|
1675 static gboolean |
|
1676 plugin_init (GstPlugin * plugin) |
|
1677 { |
|
1678 if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE, |
|
1679 GST_TYPE_TEXT_OVERLAY) || |
|
1680 !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE, |
|
1681 GST_TYPE_TIME_OVERLAY) || |
|
1682 !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE, |
|
1683 GST_TYPE_CLOCK_OVERLAY) || |
|
1684 !gst_element_register (plugin, "textrender", GST_RANK_NONE, |
|
1685 GST_TYPE_TEXT_RENDER)) { |
|
1686 return FALSE; |
|
1687 } |
|
1688 |
|
1689 /*texttestsrc_plugin_init(module, plugin); */ |
|
1690 |
|
1691 GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements"); |
|
1692 |
|
1693 return TRUE; |
|
1694 } |
|
1695 |
|
1696 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, |
|
1697 "pango", "Pango-based text rendering and overlay", plugin_init, |
|
1698 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |
|