Branch data Line data Source code
1 : : /*
2 : : * Copyright (c) 2008 Benjamin Schmitz <vortex@wolpzone.de>
3 : : * Copyright (c) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
4 : : *
5 : : * This library is free software; you can redistribute it and/or
6 : : * modify it under the terms of the GNU Library General Public
7 : : * License as published by the Free Software Foundation; either
8 : : * version 2 of the License, or (at your option) any later version.
9 : : *
10 : : * This library is distributed in the hope that it will be useful,
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : : * Library General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU Library General Public
16 : : * License along with this library; if not, write to the
17 : : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 : : * Boston, MA 02111-1307, USA.
19 : : */
20 : :
21 : : /**
22 : : * SECTION:element-assrender
23 : : *
24 : : * Renders timestamped SSA/ASS subtitles on top of a video stream.
25 : : *
26 : : * <refsect2>
27 : : * <title>Example launch line</title>
28 : : * |[
29 : : * gst-launch -v filesrc location=/path/to/mkv ! matroskademux name=d ! queue ! mp3parse ! mad ! audioconvert ! autoaudiosink d. ! queue ! ffdec_h264 ! ffmpegcolorspace ! r. d. ! queue ! "application/x-ass" ! assrender name=r ! ffmpegcolorspace ! autovideosink
30 : : * ]| This pipeline demuxes a Matroska file with h.264 video, MP3 audio and embedded ASS subtitles and renders the subtitles on top of the video.
31 : : * </refsect2>
32 : : */
33 : :
34 : :
35 : : #ifdef HAVE_CONFIG_H
36 : : # include <config.h>
37 : : #endif
38 : :
39 : : #include "gstassrender.h"
40 : :
41 : : #include <string.h>
42 : :
43 : : GST_DEBUG_CATEGORY_STATIC (gst_ass_render_debug);
44 : : GST_DEBUG_CATEGORY_STATIC (gst_ass_render_lib_debug);
45 : : #define GST_CAT_DEFAULT gst_ass_render_debug
46 : :
47 : : /* Filter signals and props */
48 : : enum
49 : : {
50 : : LAST_SIGNAL
51 : : };
52 : :
53 : : enum
54 : : {
55 : : PROP_0,
56 : : PROP_ENABLE,
57 : : PROP_EMBEDDEDFONTS
58 : : };
59 : :
60 : : static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
61 : : GST_PAD_SRC,
62 : : GST_PAD_ALWAYS,
63 : : GST_STATIC_CAPS (GST_VIDEO_CAPS_RGB ";" GST_VIDEO_CAPS_BGR ";"
64 : : GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_xBGR ";"
65 : : GST_VIDEO_CAPS_RGBx ";" GST_VIDEO_CAPS_BGRx ";"
66 : : GST_VIDEO_CAPS_YUV ("I420"))
67 : : );
68 : :
69 : : static GstStaticPadTemplate video_sink_factory =
70 : : GST_STATIC_PAD_TEMPLATE ("video_sink",
71 : : GST_PAD_SINK,
72 : : GST_PAD_ALWAYS,
73 : : GST_STATIC_CAPS (GST_VIDEO_CAPS_RGB ";" GST_VIDEO_CAPS_BGR ";"
74 : : GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_xBGR ";"
75 : : GST_VIDEO_CAPS_RGBx ";" GST_VIDEO_CAPS_BGRx ";"
76 : : GST_VIDEO_CAPS_YUV ("I420"))
77 : : );
78 : :
79 : : static GstStaticPadTemplate text_sink_factory =
80 : : GST_STATIC_PAD_TEMPLATE ("text_sink",
81 : : GST_PAD_SINK,
82 : : GST_PAD_ALWAYS,
83 : : GST_STATIC_CAPS ("application/x-ass; application/x-ssa")
84 : : );
85 : :
86 : : static void gst_ass_render_set_property (GObject * object, guint prop_id,
87 : : const GValue * value, GParamSpec * pspec);
88 : : static void gst_ass_render_get_property (GObject * object, guint prop_id,
89 : : GValue * value, GParamSpec * pspec);
90 : :
91 : : static void gst_ass_render_finalize (GObject * object);
92 : :
93 : : static GstStateChangeReturn gst_ass_render_change_state (GstElement * element,
94 : : GstStateChange transition);
95 : :
96 [ + + ]: 118 : GST_BOILERPLATE (GstAssRender, gst_ass_render, GstElement, GST_TYPE_ELEMENT);
97 : :
98 : : static GstCaps *gst_ass_render_getcaps (GstPad * pad);
99 : :
100 : : static gboolean gst_ass_render_setcaps_video (GstPad * pad, GstCaps * caps);
101 : : static gboolean gst_ass_render_setcaps_text (GstPad * pad, GstCaps * caps);
102 : :
103 : : static GstFlowReturn gst_ass_render_chain_video (GstPad * pad, GstBuffer * buf);
104 : : static GstFlowReturn gst_ass_render_chain_text (GstPad * pad, GstBuffer * buf);
105 : :
106 : : static gboolean gst_ass_render_event_video (GstPad * pad, GstEvent * event);
107 : : static gboolean gst_ass_render_event_text (GstPad * pad, GstEvent * event);
108 : : static gboolean gst_ass_render_event_src (GstPad * pad, GstEvent * event);
109 : :
110 : : static GstFlowReturn gst_ass_render_bufferalloc_video (GstPad * pad,
111 : : guint64 offset, guint size, GstCaps * caps, GstBuffer ** buffer);
112 : :
113 : : static gboolean gst_ass_render_query_src (GstPad * pad, GstQuery * query);
114 : :
115 : : static void
116 : 8 : gst_ass_render_base_init (gpointer gclass)
117 : : {
118 : 8 : GstElementClass *element_class = (GstElementClass *) gclass;
119 : :
120 : 8 : gst_element_class_add_pad_template (element_class,
121 : : gst_static_pad_template_get (&src_factory));
122 : 8 : gst_element_class_add_pad_template (element_class,
123 : : gst_static_pad_template_get (&video_sink_factory));
124 : 8 : gst_element_class_add_pad_template (element_class,
125 : : gst_static_pad_template_get (&text_sink_factory));
126 : :
127 : 8 : gst_element_class_set_details_simple (element_class, "ASS/SSA Render",
128 : : "Mixer/Video/Overlay/Subtitle",
129 : : "Renders ASS/SSA subtitles with libass",
130 : : "Benjamin Schmitz <vortex@wolpzone.de>, "
131 : : "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
132 : 8 : }
133 : :
134 : : /* initialize the plugin's class */
135 : : static void
136 : 8 : gst_ass_render_class_init (GstAssRenderClass * klass)
137 : : {
138 : 8 : GObjectClass *gobject_class = (GObjectClass *) klass;
139 : 8 : GstElementClass *gstelement_class = (GstElementClass *) klass;
140 : :
141 : 8 : gobject_class->set_property = gst_ass_render_set_property;
142 : 8 : gobject_class->get_property = gst_ass_render_get_property;
143 : 8 : gobject_class->finalize = gst_ass_render_finalize;
144 : :
145 : 8 : g_object_class_install_property (gobject_class, PROP_ENABLE,
146 : : g_param_spec_boolean ("enable", "Enable",
147 : : "Enable rendering of subtitles", TRUE,
148 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
149 : 8 : g_object_class_install_property (gobject_class, PROP_EMBEDDEDFONTS,
150 : : g_param_spec_boolean ("embeddedfonts", "Embedded Fonts",
151 : : "Extract and use fonts embedded in the stream", TRUE,
152 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
153 : :
154 : 8 : gstelement_class->change_state =
155 : 8 : GST_DEBUG_FUNCPTR (gst_ass_render_change_state);
156 : 8 : }
157 : :
158 : : #if defined(LIBASS_VERSION) && LIBASS_VERSION >= 0x00907000
159 : : static void
160 : 90 : _libass_message_cb (gint level, const gchar * fmt, va_list args,
161 : : gpointer render)
162 : : {
163 : 90 : gchar *message = g_strdup_vprintf (fmt, args);
164 : :
165 [ - + ]: 90 : if (level < 2)
166 [ # # ]: 0 : GST_CAT_ERROR_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
167 [ + + ]: 90 : else if (level < 4)
168 [ - + ]: 4 : GST_CAT_WARNING_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
169 [ - + ]: 86 : else if (level < 5)
170 [ # # ]: 0 : GST_CAT_INFO_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
171 [ - + ]: 86 : else if (level < 6)
172 [ # # ]: 0 : GST_CAT_DEBUG_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
173 : : else
174 [ - + ]: 86 : GST_CAT_LOG_OBJECT (gst_ass_render_lib_debug, render, "%s", message);
175 : :
176 : 90 : g_free (message);
177 : 90 : }
178 : : #endif
179 : :
180 : : static void
181 : 6 : gst_ass_render_init (GstAssRender * render, GstAssRenderClass * gclass)
182 : : {
183 [ - + ]: 6 : GST_DEBUG_OBJECT (render, "init");
184 : :
185 : 6 : render->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
186 : 6 : render->video_sinkpad =
187 : 6 : gst_pad_new_from_static_template (&video_sink_factory, "video_sink");
188 : 6 : render->text_sinkpad =
189 : 6 : gst_pad_new_from_static_template (&text_sink_factory, "text_sink");
190 : :
191 : 6 : gst_pad_set_setcaps_function (render->video_sinkpad,
192 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_setcaps_video));
193 : 6 : gst_pad_set_setcaps_function (render->text_sinkpad,
194 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_setcaps_text));
195 : :
196 : 6 : gst_pad_set_getcaps_function (render->srcpad,
197 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_getcaps));
198 : 6 : gst_pad_set_getcaps_function (render->video_sinkpad,
199 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_getcaps));
200 : :
201 : 6 : gst_pad_set_chain_function (render->video_sinkpad,
202 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_chain_video));
203 : 6 : gst_pad_set_chain_function (render->text_sinkpad,
204 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_chain_text));
205 : :
206 : 6 : gst_pad_set_event_function (render->video_sinkpad,
207 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_event_video));
208 : 6 : gst_pad_set_event_function (render->text_sinkpad,
209 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_event_text));
210 : 6 : gst_pad_set_event_function (render->srcpad,
211 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_event_src));
212 : :
213 : 6 : gst_pad_set_bufferalloc_function (render->video_sinkpad,
214 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_bufferalloc_video));
215 : :
216 : 6 : gst_pad_set_query_function (render->srcpad,
217 : 6 : GST_DEBUG_FUNCPTR (gst_ass_render_query_src));
218 : :
219 : 6 : gst_element_add_pad (GST_ELEMENT (render), render->srcpad);
220 : 6 : gst_element_add_pad (GST_ELEMENT (render), render->video_sinkpad);
221 : 6 : gst_element_add_pad (GST_ELEMENT (render), render->text_sinkpad);
222 : :
223 : 6 : render->width = 0;
224 : 6 : render->height = 0;
225 : :
226 : 6 : render->subtitle_mutex = g_mutex_new ();
227 : 6 : render->subtitle_cond = g_cond_new ();
228 : :
229 : 6 : render->renderer_init_ok = FALSE;
230 : 6 : render->track_init_ok = FALSE;
231 : 6 : render->enable = TRUE;
232 : 6 : render->embeddedfonts = TRUE;
233 : :
234 : 6 : gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
235 : 6 : gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
236 : :
237 : 6 : render->ass_mutex = g_mutex_new ();
238 : 6 : render->ass_library = ass_library_init ();
239 : : #if defined(LIBASS_VERSION) && LIBASS_VERSION >= 0x00907000
240 : 6 : ass_set_message_cb (render->ass_library, _libass_message_cb, render);
241 : : #endif
242 : 6 : ass_set_extract_fonts (render->ass_library, 1);
243 : :
244 : 6 : render->ass_renderer = ass_renderer_init (render->ass_library);
245 [ - + ]: 6 : if (!render->ass_renderer) {
246 [ # # ]: 0 : GST_WARNING_OBJECT (render, "cannot create renderer instance");
247 : 0 : g_assert_not_reached ();
248 : : }
249 : :
250 : 6 : render->ass_track = NULL;
251 : :
252 [ - + ]: 6 : GST_DEBUG_OBJECT (render, "init complete");
253 : 6 : }
254 : :
255 : : static void
256 : 6 : gst_ass_render_finalize (GObject * object)
257 : : {
258 : 6 : GstAssRender *render = GST_ASS_RENDER (object);
259 : :
260 [ + - ]: 6 : if (render->subtitle_mutex)
261 : 6 : g_mutex_free (render->subtitle_mutex);
262 : :
263 [ + - ]: 6 : if (render->subtitle_cond)
264 : 6 : g_cond_free (render->subtitle_cond);
265 : :
266 [ - + ]: 6 : if (render->ass_track) {
267 : 0 : ass_free_track (render->ass_track);
268 : : }
269 : :
270 [ + - ]: 6 : if (render->ass_renderer) {
271 : 6 : ass_renderer_done (render->ass_renderer);
272 : : }
273 : :
274 [ + - ]: 6 : if (render->ass_library) {
275 : 6 : ass_library_done (render->ass_library);
276 : : }
277 : :
278 [ + - ]: 6 : if (render->ass_mutex)
279 : 6 : g_mutex_free (render->ass_mutex);
280 : :
281 : 6 : G_OBJECT_CLASS (parent_class)->finalize (object);
282 : 6 : }
283 : :
284 : : static void
285 : 0 : gst_ass_render_set_property (GObject * object, guint prop_id,
286 : : const GValue * value, GParamSpec * pspec)
287 : : {
288 : 0 : GstAssRender *render = GST_ASS_RENDER (object);
289 : :
290 [ # # # ]: 0 : switch (prop_id) {
291 : : case PROP_ENABLE:
292 : 0 : render->enable = g_value_get_boolean (value);
293 : 0 : break;
294 : : case PROP_EMBEDDEDFONTS:
295 : 0 : render->embeddedfonts = g_value_get_boolean (value);
296 : 0 : g_mutex_lock (render->ass_mutex);
297 : 0 : ass_set_extract_fonts (render->ass_library, render->embeddedfonts);
298 : 0 : g_mutex_unlock (render->ass_mutex);
299 : 0 : break;
300 : : default:
301 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
302 : 0 : break;
303 : : }
304 : 0 : }
305 : :
306 : : static void
307 : 2 : gst_ass_render_get_property (GObject * object, guint prop_id,
308 : : GValue * value, GParamSpec * pspec)
309 : : {
310 : 2 : GstAssRender *render = GST_ASS_RENDER (object);
311 : :
312 [ + + - ]: 2 : switch (prop_id) {
313 : : case PROP_ENABLE:
314 : 1 : g_value_set_boolean (value, render->enable);
315 : 1 : break;
316 : : case PROP_EMBEDDEDFONTS:
317 : 1 : g_value_set_boolean (value, render->embeddedfonts);
318 : 1 : break;
319 : : default:
320 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
321 : 0 : break;
322 : : }
323 : 2 : }
324 : :
325 : : static GstStateChangeReturn
326 : 46 : gst_ass_render_change_state (GstElement * element, GstStateChange transition)
327 : : {
328 : 46 : GstAssRender *render = GST_ASS_RENDER (element);
329 : : GstStateChangeReturn ret;
330 : :
331 [ + + + ]: 46 : switch (transition) {
332 : : case GST_STATE_CHANGE_READY_TO_PAUSED:
333 : 9 : render->subtitle_flushing = FALSE;
334 : 9 : gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
335 : 9 : gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
336 : 9 : break;
337 : : case GST_STATE_CHANGE_NULL_TO_READY:
338 : : case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
339 : : default:
340 : 28 : break;
341 : :
342 : : case GST_STATE_CHANGE_PAUSED_TO_READY:
343 : 9 : g_mutex_lock (render->subtitle_mutex);
344 : 9 : render->subtitle_flushing = TRUE;
345 [ - + ]: 9 : if (render->subtitle_pending)
346 : 0 : gst_buffer_unref (render->subtitle_pending);
347 : 9 : render->subtitle_pending = NULL;
348 : 9 : g_cond_signal (render->subtitle_cond);
349 : 9 : g_mutex_unlock (render->subtitle_mutex);
350 : 9 : break;
351 : : }
352 : :
353 : 46 : ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
354 : :
355 [ + + ]: 46 : switch (transition) {
356 : : case GST_STATE_CHANGE_PAUSED_TO_READY:
357 : 9 : g_mutex_lock (render->ass_mutex);
358 [ + + ]: 9 : if (render->ass_track)
359 : 2 : ass_free_track (render->ass_track);
360 : 9 : render->ass_track = NULL;
361 : 9 : g_mutex_unlock (render->ass_mutex);
362 : 9 : render->track_init_ok = FALSE;
363 : 9 : render->renderer_init_ok = FALSE;
364 : 9 : break;
365 : : case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
366 : : case GST_STATE_CHANGE_READY_TO_NULL:
367 : : default:
368 : 37 : break;
369 : : }
370 : :
371 : :
372 : 46 : return ret;
373 : : }
374 : :
375 : : static gboolean
376 : 2 : gst_ass_render_query_src (GstPad * pad, GstQuery * query)
377 : : {
378 : 2 : GstAssRender *render = GST_ASS_RENDER (gst_pad_get_parent (pad));
379 : : gboolean ret;
380 : :
381 : 2 : ret = gst_pad_peer_query (render->video_sinkpad, query);
382 : :
383 : 2 : gst_object_unref (render);
384 : 2 : return ret;
385 : : }
386 : :
387 : : static gboolean
388 : 2 : gst_ass_render_event_src (GstPad * pad, GstEvent * event)
389 : : {
390 : 2 : GstAssRender *render = GST_ASS_RENDER (gst_pad_get_parent (pad));
391 : 2 : gboolean ret = FALSE;
392 : :
393 [ - + ]: 2 : switch (GST_EVENT_TYPE (event)) {
394 : : case GST_EVENT_SEEK:{
395 : : GstSeekFlags flags;
396 : :
397 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "seek received, driving from here");
398 : :
399 : 0 : gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
400 : :
401 : : /* Flush downstream, only for flushing seek */
402 [ # # ]: 0 : if (flags & GST_SEEK_FLAG_FLUSH)
403 : 0 : gst_pad_push_event (render->srcpad, gst_event_new_flush_start ());
404 : :
405 : : /* Mark subtitle as flushing, unblocks chains */
406 : 0 : g_mutex_lock (render->subtitle_mutex);
407 [ # # ]: 0 : if (render->subtitle_pending)
408 : 0 : gst_buffer_unref (render->subtitle_pending);
409 : 0 : render->subtitle_pending = NULL;
410 : 0 : render->subtitle_flushing = TRUE;
411 : 0 : g_cond_signal (render->subtitle_cond);
412 : 0 : g_mutex_unlock (render->subtitle_mutex);
413 : :
414 : : /* Seek on each sink pad */
415 : 0 : gst_event_ref (event);
416 : 0 : ret = gst_pad_push_event (render->video_sinkpad, event);
417 [ # # ]: 0 : if (ret) {
418 : 0 : ret = gst_pad_push_event (render->text_sinkpad, event);
419 : : } else {
420 : 0 : gst_event_unref (event);
421 : : }
422 : 0 : break;
423 : : }
424 : : default:
425 : 2 : gst_event_ref (event);
426 : 2 : ret = gst_pad_push_event (render->video_sinkpad, event);
427 : 2 : gst_pad_push_event (render->text_sinkpad, event);
428 : 2 : break;
429 : : }
430 : :
431 : 2 : gst_object_unref (render);
432 : :
433 : 2 : return ret;
434 : : }
435 : :
436 : : static GstCaps *
437 : 10 : gst_ass_render_getcaps (GstPad * pad)
438 : : {
439 : 10 : GstAssRender *render = GST_ASS_RENDER (gst_pad_get_parent (pad));
440 : : GstPad *otherpad;
441 : : GstCaps *caps;
442 : :
443 [ + + ]: 10 : if (pad == render->srcpad)
444 : 2 : otherpad = render->video_sinkpad;
445 : : else
446 : 8 : otherpad = render->srcpad;
447 : :
448 : : /* we can do what the peer can */
449 : 10 : caps = gst_pad_peer_get_caps (otherpad);
450 [ + + ]: 10 : if (caps) {
451 : : GstCaps *temp;
452 : : const GstCaps *templ;
453 : :
454 : : /* filtered against our padtemplate */
455 : 8 : templ = gst_pad_get_pad_template_caps (otherpad);
456 : 8 : temp = gst_caps_intersect (caps, templ);
457 : 8 : gst_caps_unref (caps);
458 : : /* this is what we can do */
459 : 8 : caps = temp;
460 : : } else {
461 : : /* no peer, our padtemplate is enough then */
462 : 2 : caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
463 : : }
464 : :
465 : 10 : gst_object_unref (render);
466 : :
467 : 10 : return caps;
468 : : }
469 : :
470 : : #define CREATE_RGB_BLIT_FUNCTION(name,bpp,R,G,B) \
471 : : static void \
472 : : blit_##name (GstAssRender * render, ASS_Image * ass_image, GstBuffer * buffer) \
473 : : { \
474 : : guint counter = 0; \
475 : : gint alpha, r, g, b, k; \
476 : : const guint8 *src; \
477 : : guint8 *dst; \
478 : : gint x, y, w, h; \
479 : : gint width = render->width; \
480 : : gint height = render->height; \
481 : : gint dst_stride = GST_ROUND_UP_4 (width * bpp); \
482 : : gint dst_skip; \
483 : : gint src_skip; \
484 : : \
485 : : while (ass_image) { \
486 : : if (ass_image->dst_y > height || ass_image->dst_x > width) \
487 : : goto next; \
488 : : \
489 : : /* blend subtitles onto the video frame */ \
490 : : alpha = 255 - ((ass_image->color) & 0xff); \
491 : : r = ((ass_image->color) >> 24) & 0xff; \
492 : : g = ((ass_image->color) >> 16) & 0xff; \
493 : : b = ((ass_image->color) >> 8) & 0xff; \
494 : : src = ass_image->bitmap; \
495 : : dst = buffer->data + ass_image->dst_y * dst_stride + ass_image->dst_x * bpp; \
496 : : \
497 : : w = MIN (ass_image->w, width - ass_image->dst_x); \
498 : : h = MIN (ass_image->h, height - ass_image->dst_y); \
499 : : src_skip = ass_image->stride - w; \
500 : : dst_skip = dst_stride - w * bpp; \
501 : : \
502 : : for (y = 0; y < h; y++) { \
503 : : for (x = 0; x < w; x++) { \
504 : : k = src[0] * alpha / 255; \
505 : : dst[R] = (k * r + (255 - k) * dst[R]) / 255; \
506 : : dst[G] = (k * g + (255 - k) * dst[G]) / 255; \
507 : : dst[B] = (k * b + (255 - k) * dst[B]) / 255; \
508 : : src++; \
509 : : dst += bpp; \
510 : : } \
511 : : src += src_skip; \
512 : : dst += dst_skip; \
513 : : } \
514 : : next: \
515 : : counter++; \
516 : : ass_image = ass_image->next; \
517 : : } \
518 : : GST_LOG_OBJECT (render, "amount of rendered ass_image: %u", counter); \
519 : : }
520 : :
521 [ # # ][ # # ]: 0 : CREATE_RGB_BLIT_FUNCTION (rgb, 3, 0, 1, 2);
[ # # ][ # # ]
[ # # ][ # # ]
522 [ # # ][ # # ]: 0 : CREATE_RGB_BLIT_FUNCTION (bgr, 3, 2, 1, 0);
[ # # ][ # # ]
[ # # ][ # # ]
523 [ + - ][ + - ]: 129262 : CREATE_RGB_BLIT_FUNCTION (xrgb, 4, 1, 2, 3);
[ + + ][ + + ]
[ + + ][ - + ]
524 [ # # ][ # # ]: 0 : CREATE_RGB_BLIT_FUNCTION (xbgr, 4, 3, 2, 1);
[ # # ][ # # ]
[ # # ][ # # ]
525 [ # # ][ # # ]: 0 : CREATE_RGB_BLIT_FUNCTION (rgbx, 4, 0, 1, 2);
[ # # ][ # # ]
[ # # ][ # # ]
526 [ # # ][ # # ]: 0 : CREATE_RGB_BLIT_FUNCTION (bgrx, 4, 2, 1, 0);
[ # # ][ # # ]
[ # # ][ # # ]
527 : :
528 : : #undef CREATE_RGB_BLIT_FUNCTION
529 : :
530 : : static inline gint
531 : 96 : rgb_to_y (gint r, gint g, gint b)
532 : : {
533 : : gint ret;
534 : :
535 : 96 : ret = (gint) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16));
536 [ + - ]: 96 : ret = CLAMP (ret, 0, 255);
537 : 96 : return ret;
538 : : }
539 : :
540 : : static inline gint
541 : 96 : rgb_to_u (gint r, gint g, gint b)
542 : : {
543 : : gint ret;
544 : :
545 : 96 : ret =
546 : 96 : (gint) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) +
547 : : 128);
548 [ + - ]: 96 : ret = CLAMP (ret, 0, 255);
549 : 96 : return ret;
550 : : }
551 : :
552 : : static inline gint
553 : 96 : rgb_to_v (gint r, gint g, gint b)
554 : : {
555 : : gint ret;
556 : :
557 : 96 : ret =
558 : 96 : (gint) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) +
559 : : 128);
560 [ + - ]: 96 : ret = CLAMP (ret, 0, 255);
561 : 96 : return ret;
562 : : }
563 : :
564 : : static void
565 : 2 : blit_i420 (GstAssRender * render, ASS_Image * ass_image, GstBuffer * buffer)
566 : : {
567 : 2 : guint counter = 0;
568 : : gint alpha, r, g, b, k, k2;
569 : : gint Y, U, V;
570 : : const guint8 *src;
571 : : guint8 *dst_y, *dst_u, *dst_v;
572 : : gint x, y, w, h;
573 : : gint w2;
574 : 2 : gint width = render->width;
575 : 2 : gint height = render->height;
576 : : gint src_stride;
577 : : gint y_offset, y_stride;
578 : : gint u_offset, u_stride;
579 : : gint v_offset, v_stride;
580 : :
581 : 2 : y_offset =
582 : : gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 0, width,
583 : : height);
584 : 2 : u_offset =
585 : : gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 1, width,
586 : : height);
587 : 2 : v_offset =
588 : : gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 2, width,
589 : : height);
590 : :
591 : 2 : y_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0, width);
592 : 2 : u_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1, width);
593 : 2 : v_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 2, width);
594 : :
595 [ + + ]: 98 : while (ass_image) {
596 [ + - ][ + - ]: 96 : if (ass_image->dst_y > height || ass_image->dst_x > width)
597 : : goto next;
598 : :
599 : : /* blend subtitles onto the video frame */
600 : 96 : alpha = 255 - ((ass_image->color) & 0xff);
601 : 96 : r = ((ass_image->color) >> 24) & 0xff;
602 : 96 : g = ((ass_image->color) >> 16) & 0xff;
603 : 96 : b = ((ass_image->color) >> 8) & 0xff;
604 : :
605 : 96 : Y = rgb_to_y (r, g, b);
606 : 96 : U = rgb_to_u (r, g, b);
607 : 96 : V = rgb_to_v (r, g, b);
608 : :
609 : 96 : w = MIN (ass_image->w, width - ass_image->dst_x);
610 : 96 : h = MIN (ass_image->h, height - ass_image->dst_y);
611 : :
612 : 96 : w2 = (w + 1) / 2;
613 : :
614 : 96 : src_stride = ass_image->stride;
615 : :
616 : 96 : src = ass_image->bitmap;
617 : 96 : dst_y =
618 : 192 : buffer->data + y_offset + ass_image->dst_y * y_stride +
619 : 96 : ass_image->dst_x;
620 : 96 : dst_u =
621 : 192 : buffer->data + u_offset + ((ass_image->dst_y + 1) / 2) * u_stride +
622 : 96 : (ass_image->dst_x + 1) / 2;
623 : 96 : dst_v =
624 : 192 : buffer->data + v_offset + ((ass_image->dst_y + 1) / 2) * v_stride +
625 : 96 : (ass_image->dst_x + 1) / 2;
626 : :
627 [ + + ]: 1906 : for (y = 0; y < h - 1; y += 2) {
628 [ + + ]: 32456 : for (x = 0; x < w - 1; x += 2) {
629 : 30646 : k = src[0] * alpha / 255;
630 : 30646 : k2 = k;
631 : 30646 : dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
632 : :
633 : 30646 : k = src[1] * alpha / 255;
634 : 30646 : k2 += k;
635 : 30646 : dst_y[1] = (k * Y + (255 - k) * dst_y[1]) / 255;
636 : :
637 : 30646 : src += src_stride;
638 : 30646 : dst_y += y_stride;
639 : :
640 : 30646 : k = src[0] * alpha / 255;
641 : 30646 : k2 += k;
642 : 30646 : dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
643 : :
644 : 30646 : k = src[1] * alpha / 255;
645 : 30646 : k2 += k;
646 : 30646 : dst_y[1] = (k * Y + (255 - k) * dst_y[1]) / 255;
647 : :
648 : 30646 : k2 /= 4;
649 : 30646 : dst_u[0] = (k2 * U + (255 - k2) * dst_u[0]) / 255;
650 : 30646 : dst_v[0] = (k2 * V + (255 - k2) * dst_v[0]) / 255;
651 : 30646 : dst_u++;
652 : 30646 : dst_v++;
653 : :
654 : 30646 : src += -src_stride + 2;
655 : 30646 : dst_y += -y_stride + 2;
656 : : }
657 : :
658 [ + + ]: 1810 : if (x < w) {
659 : 826 : k = src[0] * alpha / 255;
660 : 826 : k2 = k;
661 : 826 : dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
662 : :
663 : 826 : src += src_stride;
664 : 826 : dst_y += y_stride;
665 : :
666 : 826 : k = src[0] * alpha / 255;
667 : 826 : k2 += k;
668 : 826 : dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
669 : :
670 : 826 : k2 /= 2;
671 : 826 : dst_u[0] = (k2 * U + (255 - k2) * dst_u[0]) / 255;
672 : 826 : dst_v[0] = (k2 * V + (255 - k2) * dst_v[0]) / 255;
673 : 826 : dst_u++;
674 : 826 : dst_v++;
675 : :
676 : 826 : src += -src_stride + 1;
677 : 826 : dst_y += -y_stride + 1;
678 : : }
679 : :
680 : 1810 : src += src_stride + (src_stride - w);
681 : 1810 : dst_y += y_stride + (y_stride - w);
682 : 1810 : dst_u += u_stride - w2;
683 : 1810 : dst_v += v_stride - w2;
684 : : }
685 : :
686 [ + + ]: 96 : if (y < h) {
687 [ + + ]: 662 : for (x = 0; x < w - 1; x += 2) {
688 : 626 : k = src[0] * alpha / 255;
689 : 626 : k2 = k;
690 : 626 : dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
691 : :
692 : 626 : k = src[1] * alpha / 255;
693 : 626 : k2 += k;
694 : 626 : dst_y[1] = (k * Y + (255 - k) * dst_y[1]) / 255;
695 : :
696 : 626 : k2 /= 2;
697 : 626 : dst_u[0] = (k2 * U + (255 - k2) * dst_u[0]) / 255;
698 : 626 : dst_v[0] = (k2 * V + (255 - k2) * dst_v[0]) / 255;
699 : 626 : dst_u++;
700 : 626 : dst_v++;
701 : :
702 : 626 : src += 2;
703 : 626 : dst_y += 2;
704 : : }
705 : :
706 [ + + ]: 36 : if (x < w) {
707 : 20 : k = src[0] * alpha / 255;
708 : 20 : k2 = k;
709 : 20 : dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
710 : :
711 : 20 : dst_u[0] = (k2 * U + (255 - k2) * dst_u[0]) / 255;
712 : 20 : dst_v[0] = (k2 * V + (255 - k2) * dst_v[0]) / 255;
713 : : }
714 : : }
715 : :
716 : : next:
717 : 96 : counter++;
718 : 96 : ass_image = ass_image->next;
719 : : }
720 : :
721 [ - + ]: 2 : GST_LOG_OBJECT (render, "amount of rendered ass_image: %u", counter);
722 : 2 : }
723 : :
724 : : static gboolean
725 : 2 : gst_ass_render_setcaps_video (GstPad * pad, GstCaps * caps)
726 : : {
727 : 2 : GstAssRender *render = GST_ASS_RENDER (gst_pad_get_parent (pad));
728 : 2 : gboolean ret = FALSE;
729 : 2 : gint par_n = 1, par_d = 1;
730 : : gdouble dar;
731 : :
732 : 2 : render->width = 0;
733 : 2 : render->height = 0;
734 : :
735 [ + - ]: 2 : if (!gst_video_format_parse_caps (caps, &render->format, &render->width,
736 [ - + ]: 2 : &render->height) ||
737 : 2 : !gst_video_parse_caps_framerate (caps, &render->fps_n, &render->fps_d)) {
738 [ # # ]: 0 : GST_ERROR_OBJECT (render, "Can't parse caps: %" GST_PTR_FORMAT, caps);
739 : 0 : ret = FALSE;
740 : 0 : goto out;
741 : : }
742 : :
743 : 2 : gst_video_parse_caps_pixel_aspect_ratio (caps, &par_n, &par_d);
744 : :
745 : 2 : ret = gst_pad_set_caps (render->srcpad, caps);
746 [ - + ]: 2 : if (!ret)
747 : 0 : goto out;
748 : :
749 [ - - + - : 2 : switch (render->format) {
- - + - ]
750 : : case GST_VIDEO_FORMAT_RGB:
751 : 0 : render->blit = blit_rgb;
752 : 0 : break;
753 : : case GST_VIDEO_FORMAT_BGR:
754 : 0 : render->blit = blit_bgr;
755 : 0 : break;
756 : : case GST_VIDEO_FORMAT_xRGB:
757 : 1 : render->blit = blit_xrgb;
758 : 1 : break;
759 : : case GST_VIDEO_FORMAT_xBGR:
760 : 0 : render->blit = blit_xbgr;
761 : 0 : break;
762 : : case GST_VIDEO_FORMAT_RGBx:
763 : 0 : render->blit = blit_rgbx;
764 : 0 : break;
765 : : case GST_VIDEO_FORMAT_BGRx:
766 : 0 : render->blit = blit_bgrx;
767 : 0 : break;
768 : : case GST_VIDEO_FORMAT_I420:
769 : 1 : render->blit = blit_i420;
770 : 1 : break;
771 : : default:
772 : 0 : ret = FALSE;
773 : 0 : goto out;
774 : : }
775 : :
776 : 2 : g_mutex_lock (render->ass_mutex);
777 : 2 : ass_set_frame_size (render->ass_renderer, render->width, render->height);
778 : :
779 : 4 : dar = (((gdouble) par_n) * ((gdouble) render->width))
780 : 2 : / (((gdouble) par_d) * ((gdouble) render->height));
781 : : #if !defined(LIBASS_VERSION) || LIBASS_VERSION < 0x00907000
782 : : ass_set_aspect_ratio (render->ass_renderer, dar);
783 : : #else
784 : 2 : ass_set_aspect_ratio (render->ass_renderer,
785 : 2 : dar, ((gdouble) render->width) / ((gdouble) render->height));
786 : : #endif
787 : 2 : ass_set_font_scale (render->ass_renderer, 1.0);
788 : 2 : ass_set_hinting (render->ass_renderer, ASS_HINTING_LIGHT);
789 : :
790 : : #if !defined(LIBASS_VERSION) || LIBASS_VERSION < 0x00907000
791 : : ass_set_fonts (render->ass_renderer, "Arial", "sans-serif");
792 : : ass_set_fonts (render->ass_renderer, NULL, "Sans");
793 : : #else
794 : 2 : ass_set_fonts (render->ass_renderer, "Arial", "sans-serif", 1, NULL, 1);
795 : 2 : ass_set_fonts (render->ass_renderer, NULL, "Sans", 1, NULL, 1);
796 : : #endif
797 : 2 : ass_set_margins (render->ass_renderer, 0, 0, 0, 0);
798 : 2 : ass_set_use_margins (render->ass_renderer, 0);
799 : 2 : g_mutex_unlock (render->ass_mutex);
800 : :
801 : 2 : render->renderer_init_ok = TRUE;
802 : :
803 [ - + ]: 2 : GST_DEBUG_OBJECT (render, "ass renderer setup complete");
804 : :
805 : : out:
806 : 2 : gst_object_unref (render);
807 : :
808 : 2 : return ret;
809 : : }
810 : :
811 : : static gboolean
812 : 2 : gst_ass_render_setcaps_text (GstPad * pad, GstCaps * caps)
813 : : {
814 : 2 : GstAssRender *render = GST_ASS_RENDER (gst_pad_get_parent (pad));
815 : : GstStructure *structure;
816 : : const GValue *value;
817 : : GstBuffer *priv;
818 : : gchar *codec_private;
819 : : guint codec_private_size;
820 : 2 : gboolean ret = FALSE;
821 : :
822 : 2 : structure = gst_caps_get_structure (caps, 0);
823 : :
824 [ - + ]: 2 : GST_DEBUG_OBJECT (render, "text pad linked with caps: %" GST_PTR_FORMAT,
825 : : caps);
826 : :
827 : 2 : value = gst_structure_get_value (structure, "codec_data");
828 : :
829 : 2 : g_mutex_lock (render->ass_mutex);
830 [ + - ]: 2 : if (value != NULL) {
831 : 2 : priv = gst_value_get_buffer (value);
832 [ - + ]: 2 : g_return_val_if_fail (priv != NULL, FALSE);
833 : :
834 : 2 : codec_private = (gchar *) GST_BUFFER_DATA (priv);
835 : 2 : codec_private_size = GST_BUFFER_SIZE (priv);
836 : :
837 [ + - ]: 2 : if (!render->ass_track)
838 : 2 : render->ass_track = ass_new_track (render->ass_library);
839 : :
840 : 2 : ass_process_codec_private (render->ass_track,
841 : : codec_private, codec_private_size);
842 : :
843 [ - + ]: 2 : GST_DEBUG_OBJECT (render, "ass track created");
844 : :
845 : 2 : render->track_init_ok = TRUE;
846 : :
847 : 2 : ret = TRUE;
848 [ # # ]: 0 : } else if (!render->ass_track) {
849 : 0 : render->ass_track = ass_new_track (render->ass_library);
850 : :
851 : 0 : render->track_init_ok = TRUE;
852 : :
853 : 0 : ret = TRUE;
854 : : }
855 : 2 : g_mutex_unlock (render->ass_mutex);
856 : :
857 : 2 : gst_object_unref (render);
858 : :
859 : 2 : return ret;
860 : : }
861 : :
862 : :
863 : : static void
864 : 2 : gst_ass_render_process_text (GstAssRender * render, GstBuffer * buffer,
865 : : GstClockTime running_time, GstClockTime duration)
866 : : {
867 : 2 : gchar *data = (gchar *) GST_BUFFER_DATA (buffer);
868 : 2 : guint size = GST_BUFFER_SIZE (buffer);
869 : : gdouble pts_start, pts_end;
870 : :
871 : 2 : pts_start = running_time;
872 : 2 : pts_start /= GST_MSECOND;
873 : 2 : pts_end = duration;
874 : 2 : pts_end /= GST_MSECOND;
875 : :
876 [ - + ][ # # ]: 2 : GST_DEBUG_OBJECT (render,
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
877 : : "Processing subtitles with running time %" GST_TIME_FORMAT
878 : : " and duration %" GST_TIME_FORMAT, GST_TIME_ARGS (running_time),
879 : : GST_TIME_ARGS (duration));
880 : 2 : g_mutex_lock (render->ass_mutex);
881 : 2 : ass_process_chunk (render->ass_track, data, size, pts_start, pts_end);
882 : 2 : g_mutex_unlock (render->ass_mutex);
883 : 2 : gst_buffer_unref (buffer);
884 : 2 : }
885 : :
886 : : static GstFlowReturn
887 : 10 : gst_ass_render_bufferalloc_video (GstPad * pad, guint64 offset, guint size,
888 : : GstCaps * caps, GstBuffer ** buffer)
889 : : {
890 : 10 : GstAssRender *render = GST_ASS_RENDER (gst_pad_get_parent (pad));
891 : 10 : GstFlowReturn ret = GST_FLOW_WRONG_STATE;
892 : : GstPad *allocpad;
893 : :
894 : 10 : GST_OBJECT_LOCK (render);
895 [ + - ]: 10 : allocpad = render->srcpad ? gst_object_ref (render->srcpad) : NULL;
896 : 10 : GST_OBJECT_UNLOCK (render);
897 : :
898 [ + - ]: 10 : if (allocpad) {
899 : 10 : ret = gst_pad_alloc_buffer (allocpad, offset, size, caps, buffer);
900 : 10 : gst_object_unref (allocpad);
901 : : }
902 : :
903 : 10 : gst_object_unref (render);
904 : :
905 : 10 : return ret;
906 : : }
907 : :
908 : : static GstFlowReturn
909 : 10 : gst_ass_render_chain_video (GstPad * pad, GstBuffer * buffer)
910 : : {
911 : 10 : GstAssRender *render = GST_ASS_RENDER (GST_PAD_PARENT (pad));
912 : 10 : GstFlowReturn ret = GST_FLOW_OK;
913 : 10 : gboolean in_seg = FALSE;
914 : 10 : gint64 start, stop, clip_start = 0, clip_stop = 0;
915 : : ASS_Image *ass_image;
916 : :
917 [ - + ]: 10 : if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
918 [ # # ]: 0 : GST_WARNING_OBJECT (render, "buffer without timestamp, discarding");
919 : 0 : gst_buffer_unref (buffer);
920 : 0 : return GST_FLOW_OK;
921 : : }
922 : :
923 : : /* ignore buffers that are outside of the current segment */
924 : 10 : start = GST_BUFFER_TIMESTAMP (buffer);
925 : :
926 [ - + ]: 10 : if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
927 : 0 : stop = GST_CLOCK_TIME_NONE;
928 : : } else {
929 : 10 : stop = start + GST_BUFFER_DURATION (buffer);
930 : : }
931 : :
932 : : /* segment_clip() will adjust start unconditionally to segment_start if
933 : : * no stop time is provided, so handle this ourselves */
934 [ - + ][ # # ]: 10 : if (stop == GST_CLOCK_TIME_NONE && start < render->video_segment.start)
935 : 0 : goto out_of_segment;
936 : :
937 : 10 : in_seg =
938 : 10 : gst_segment_clip (&render->video_segment, GST_FORMAT_TIME, start, stop,
939 : : &clip_start, &clip_stop);
940 : :
941 [ - + ]: 10 : if (!in_seg)
942 : 0 : goto out_of_segment;
943 : :
944 : : /* if the buffer is only partially in the segment, fix up stamps */
945 [ + - ][ + - ]: 10 : if (clip_start != start || (stop != -1 && clip_stop != stop)) {
[ - + ]
946 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "clipping buffer timestamp/duration to segment");
947 : 0 : buffer = gst_buffer_make_metadata_writable (buffer);
948 : 0 : GST_BUFFER_TIMESTAMP (buffer) = clip_start;
949 [ # # ]: 0 : if (stop != -1)
950 : 0 : GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
951 : : }
952 : :
953 : 10 : gst_segment_set_last_stop (&render->video_segment, GST_FORMAT_TIME,
954 : : clip_start);
955 : :
956 : 10 : g_mutex_lock (render->subtitle_mutex);
957 [ - + ]: 10 : if (render->subtitle_pending) {
958 : : GstClockTime sub_running_time, vid_running_time;
959 : : GstClockTime sub_running_time_end, vid_running_time_end;
960 : :
961 : 0 : sub_running_time =
962 : 0 : gst_segment_to_running_time (&render->subtitle_segment, GST_FORMAT_TIME,
963 : 0 : GST_BUFFER_TIMESTAMP (render->subtitle_pending));
964 : 0 : sub_running_time_end =
965 : 0 : gst_segment_to_running_time (&render->subtitle_segment, GST_FORMAT_TIME,
966 : 0 : GST_BUFFER_TIMESTAMP (render->subtitle_pending) +
967 : 0 : GST_BUFFER_DURATION (render->subtitle_pending));
968 : 0 : vid_running_time =
969 : 0 : gst_segment_to_running_time (&render->video_segment, GST_FORMAT_TIME,
970 : 0 : GST_BUFFER_TIMESTAMP (buffer));
971 : 0 : vid_running_time_end =
972 : 0 : gst_segment_to_running_time (&render->video_segment, GST_FORMAT_TIME,
973 : 0 : GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer));
974 : :
975 [ # # ]: 0 : if (sub_running_time_end < vid_running_time) {
976 : 0 : gst_buffer_unref (render->subtitle_pending);
977 [ # # ][ # # ]: 0 : GST_DEBUG_OBJECT (render,
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
978 : : "Too late text buffer, dropping (%" GST_TIME_FORMAT " < %"
979 : : GST_TIME_FORMAT, GST_TIME_ARGS (sub_running_time_end),
980 : : GST_TIME_ARGS (vid_running_time));
981 : 0 : render->subtitle_pending = NULL;
982 : 0 : g_cond_signal (render->subtitle_cond);
983 [ # # ]: 0 : } else if (sub_running_time <= vid_running_time_end + GST_SECOND / 2) {
984 : 0 : gst_ass_render_process_text (render, render->subtitle_pending,
985 : : sub_running_time, sub_running_time_end - sub_running_time);
986 : 0 : render->subtitle_pending = NULL;
987 : 0 : g_cond_signal (render->subtitle_cond);
988 : : }
989 : : }
990 : 10 : g_mutex_unlock (render->subtitle_mutex);
991 : :
992 : : /* now start rendering subtitles, if all conditions are met */
993 [ + - ][ + - ]: 20 : if (render->renderer_init_ok && render->track_init_ok && render->enable) {
[ + - ]
994 : : GstClockTime running_time;
995 : : gdouble timestamp;
996 : : #ifndef GST_DISABLE_GST_DEBUG
997 : : gdouble step;
998 : : #endif
999 : :
1000 : 10 : running_time =
1001 : 10 : gst_segment_to_running_time (&render->video_segment, GST_FORMAT_TIME,
1002 : 10 : GST_BUFFER_TIMESTAMP (buffer));
1003 [ - + ][ # # ]: 10 : GST_DEBUG_OBJECT (render,
[ # # ][ # # ]
[ # # ]
1004 : : "rendering frame for running time %" GST_TIME_FORMAT,
1005 : : GST_TIME_ARGS (running_time));
1006 : : /* libass needs timestamps in ms */
1007 : 10 : timestamp = running_time / GST_MSECOND;
1008 : :
1009 : 10 : g_mutex_lock (render->ass_mutex);
1010 : : #ifndef GST_DISABLE_GST_DEBUG
1011 : : /* only for testing right now. could possibly be used for optimizations? */
1012 : 10 : step = ass_step_sub (render->ass_track, timestamp, 1);
1013 [ - + ][ # # ]: 10 : GST_DEBUG_OBJECT (render, "Current running time: %" GST_TIME_FORMAT
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
1014 : : " // Next event: %" GST_TIME_FORMAT,
1015 : : GST_TIME_ARGS (running_time), GST_TIME_ARGS (step * GST_MSECOND));
1016 : : #endif
1017 : :
1018 : : /* not sure what the last parameter to this call is for (detect_change) */
1019 : 10 : ass_image = ass_render_frame (render->ass_renderer, render->ass_track,
1020 : : timestamp, NULL);
1021 : 10 : g_mutex_unlock (render->ass_mutex);
1022 : :
1023 [ + + ]: 10 : if (ass_image != NULL) {
1024 : 4 : buffer = gst_buffer_make_writable (buffer);
1025 : 4 : render->blit (render, ass_image, buffer);
1026 : : } else {
1027 [ - + ]: 6 : GST_LOG_OBJECT (render, "nothing to render right now");
1028 : : }
1029 : : } else {
1030 [ # # ]: 0 : GST_LOG_OBJECT (render, "rendering disabled, doing buffer passthrough");
1031 : : }
1032 : :
1033 : 10 : ret = gst_pad_push (render->srcpad, buffer);
1034 : :
1035 : 10 : return ret;
1036 : :
1037 : : out_of_segment:
1038 : : {
1039 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "buffer out of segment, discarding");
1040 : 0 : gst_buffer_unref (buffer);
1041 : 10 : return GST_FLOW_OK;
1042 : : }
1043 : : }
1044 : :
1045 : : static GstFlowReturn
1046 : 2 : gst_ass_render_chain_text (GstPad * pad, GstBuffer * buffer)
1047 : : {
1048 : 2 : GstFlowReturn ret = GST_FLOW_OK;
1049 : 2 : GstAssRender *render = GST_ASS_RENDER (GST_PAD_PARENT (pad));
1050 : : GstClockTime timestamp, duration;
1051 : : GstClockTime sub_running_time, vid_running_time;
1052 : : GstClockTime sub_running_time_end;
1053 : : gint64 cstart, cstop;
1054 : : gboolean in_seg;
1055 : :
1056 [ - + ]: 2 : if (render->subtitle_flushing) {
1057 : 0 : gst_buffer_unref (buffer);
1058 : 0 : return GST_FLOW_WRONG_STATE;
1059 : : }
1060 : :
1061 : 2 : timestamp = GST_BUFFER_TIMESTAMP (buffer);
1062 : 2 : duration = GST_BUFFER_DURATION (buffer);
1063 : :
1064 [ + - ][ - + ]: 2 : if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (timestamp)
1065 : : || !GST_CLOCK_TIME_IS_VALID (duration))) {
1066 [ # # ]: 0 : GST_WARNING_OBJECT (render,
1067 : : "Text buffer without valid timestamp" " or duration, dropping");
1068 : 0 : gst_buffer_unref (buffer);
1069 : 0 : return GST_FLOW_OK;
1070 : : }
1071 : :
1072 : 2 : in_seg =
1073 : 2 : gst_segment_clip (&render->subtitle_segment, GST_FORMAT_TIME, timestamp,
1074 : 2 : timestamp + duration, &cstart, &cstop);
1075 [ - + ]: 2 : if (!in_seg) {
1076 [ # # ][ # # ]: 0 : GST_DEBUG_OBJECT (render,
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
1077 : : "Text buffer before segment start (%" GST_TIME_FORMAT " < %"
1078 : : GST_TIME_FORMAT ")", GST_TIME_ARGS (timestamp),
1079 : : GST_TIME_ARGS (render->subtitle_segment.start));
1080 : 0 : gst_buffer_unref (buffer);
1081 : 0 : return GST_FLOW_OK;
1082 : : }
1083 : :
1084 : 2 : GST_BUFFER_TIMESTAMP (buffer) = timestamp = cstart;
1085 : 2 : GST_BUFFER_DURATION (buffer) = duration = cstop - cstart;
1086 : :
1087 : 2 : gst_segment_set_last_stop (&render->subtitle_segment, GST_FORMAT_TIME,
1088 : 2 : GST_BUFFER_TIMESTAMP (buffer));
1089 : :
1090 : 2 : sub_running_time =
1091 : 2 : gst_segment_to_running_time (&render->subtitle_segment, GST_FORMAT_TIME,
1092 : : timestamp);
1093 : 2 : sub_running_time_end =
1094 : 2 : gst_segment_to_running_time (&render->subtitle_segment, GST_FORMAT_TIME,
1095 : 2 : timestamp + duration);
1096 : 2 : vid_running_time =
1097 : 2 : gst_segment_to_running_time (&render->video_segment, GST_FORMAT_TIME,
1098 : : render->video_segment.last_stop);
1099 : :
1100 [ - + ][ # # ]: 2 : if (render->fps_n && render->fps_d)
1101 : 0 : vid_running_time +=
1102 : 0 : gst_util_uint64_scale (GST_SECOND, render->fps_d, render->fps_n);
1103 : :
1104 [ - + ]: 2 : if (sub_running_time > vid_running_time + GST_SECOND / 2) {
1105 [ # # ]: 0 : g_assert (render->subtitle_pending == NULL);
1106 : 0 : g_mutex_lock (render->subtitle_mutex);
1107 [ # # ]: 0 : if (G_UNLIKELY (render->subtitle_flushing)) {
1108 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "Text pad flushing");
1109 : 0 : gst_object_unref (buffer);
1110 : 0 : g_mutex_unlock (render->subtitle_mutex);
1111 : 0 : return GST_FLOW_WRONG_STATE;
1112 : : }
1113 [ # # ][ # # ]: 0 : GST_DEBUG_OBJECT (render,
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
1114 : : "Too early text buffer, waiting (%" GST_TIME_FORMAT " > %"
1115 : : GST_TIME_FORMAT, GST_TIME_ARGS (sub_running_time),
1116 : : GST_TIME_ARGS (vid_running_time));
1117 : 0 : render->subtitle_pending = buffer;
1118 : 0 : g_cond_wait (render->subtitle_cond, render->subtitle_mutex);
1119 : 0 : g_mutex_unlock (render->subtitle_mutex);
1120 [ - + ]: 2 : } else if (sub_running_time_end < vid_running_time) {
1121 [ # # ][ # # ]: 0 : GST_DEBUG_OBJECT (render,
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
1122 : : "Too late text buffer, dropping (%" GST_TIME_FORMAT " < %"
1123 : : GST_TIME_FORMAT, GST_TIME_ARGS (sub_running_time_end),
1124 : : GST_TIME_ARGS (vid_running_time));
1125 : 0 : gst_buffer_unref (buffer);
1126 : 0 : ret = GST_FLOW_OK;
1127 : : } else {
1128 : 2 : gst_ass_render_process_text (render, buffer, sub_running_time,
1129 : : sub_running_time_end - sub_running_time);
1130 : 2 : ret = GST_FLOW_OK;
1131 : : }
1132 : :
1133 [ - + ][ # # ]: 2 : GST_DEBUG_OBJECT (render,
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
1134 : : "processed text packet with timestamp %" GST_TIME_FORMAT
1135 : : " and duration %" GST_TIME_FORMAT,
1136 : : GST_TIME_ARGS (timestamp), GST_TIME_ARGS (duration));
1137 : :
1138 : 2 : return ret;
1139 : : }
1140 : :
1141 : : static void
1142 : 0 : gst_ass_render_handle_tags (GstAssRender * render, GstTagList * taglist)
1143 : : {
1144 : : static const gchar *mimetypes[] = {
1145 : : "application/x-font-ttf",
1146 : : "application/x-font-otf",
1147 : : "application/x-truetype-font"
1148 : : };
1149 : : static const gchar *extensions[] = {
1150 : : ".otf",
1151 : : ".ttf"
1152 : : };
1153 : : guint tag_size;
1154 : : guint index;
1155 : :
1156 [ # # ]: 0 : if (!taglist)
1157 : 0 : return;
1158 : :
1159 : 0 : tag_size = gst_tag_list_get_tag_size (taglist, GST_TAG_ATTACHMENT);
1160 [ # # ][ # # ]: 0 : if (tag_size > 0 && render->embeddedfonts) {
1161 : : const GValue *value;
1162 : : GstBuffer *buf;
1163 : : GstCaps *caps;
1164 : : GstStructure *structure;
1165 : : gboolean valid_mimetype, valid_extension;
1166 : : guint j;
1167 : : const gchar *filename;
1168 : :
1169 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "TAG event has attachments");
1170 : :
1171 [ # # ]: 0 : for (index = 0; index < tag_size; index++) {
1172 : 0 : value = gst_tag_list_get_value_index (taglist, GST_TAG_ATTACHMENT, index);
1173 : 0 : buf = gst_value_get_buffer (value);
1174 [ # # ][ # # ]: 0 : if (!buf || !GST_BUFFER_CAPS (buf))
1175 : 0 : continue;
1176 : :
1177 : 0 : caps = GST_BUFFER_CAPS (buf);
1178 : 0 : structure = gst_caps_get_structure (caps, 0);
1179 : :
1180 : 0 : valid_mimetype = FALSE;
1181 : 0 : valid_extension = FALSE;
1182 : :
1183 [ # # ]: 0 : for (j = 0; j < G_N_ELEMENTS (mimetypes); j++) {
1184 [ # # ]: 0 : if (gst_structure_has_name (structure, mimetypes[j])) {
1185 : 0 : valid_mimetype = TRUE;
1186 : 0 : break;
1187 : : }
1188 : : }
1189 : 0 : filename = gst_structure_get_string (structure, "filename");
1190 [ # # ]: 0 : if (!filename)
1191 : 0 : continue;
1192 : :
1193 [ # # ]: 0 : if (!valid_mimetype) {
1194 : 0 : guint len = strlen (filename);
1195 : 0 : const gchar *extension = filename + len - 4;
1196 [ # # ]: 0 : for (j = 0; j < G_N_ELEMENTS (extensions); j++) {
1197 [ # # ]: 0 : if (g_ascii_strcasecmp (extension, extensions[j]) == 0) {
1198 : 0 : valid_extension = TRUE;
1199 : 0 : break;
1200 : : }
1201 : : }
1202 : : }
1203 : :
1204 [ # # ][ # # ]: 0 : if (valid_mimetype || valid_extension) {
1205 : 0 : g_mutex_lock (render->ass_mutex);
1206 : 0 : ass_add_font (render->ass_library, (gchar *) filename,
1207 : 0 : (gchar *) GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
1208 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "registered new font %s", filename);
1209 : 0 : g_mutex_unlock (render->ass_mutex);
1210 : : }
1211 : : }
1212 : : }
1213 : : }
1214 : :
1215 : : static gboolean
1216 : 4 : gst_ass_render_event_video (GstPad * pad, GstEvent * event)
1217 : : {
1218 : 4 : gboolean ret = FALSE;
1219 : 4 : GstAssRender *render = GST_ASS_RENDER (gst_pad_get_parent (pad));
1220 : :
1221 [ - + ]: 4 : GST_DEBUG_OBJECT (pad, "received video event %s",
1222 : : GST_EVENT_TYPE_NAME (event));
1223 : :
1224 [ + - - + ]: 4 : 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 [ - + ]: 2 : GST_DEBUG_OBJECT (render, "received new segment");
1233 : :
1234 : 2 : gst_event_parse_new_segment (event, &update, &rate, &format, &start,
1235 : : &stop, &time);
1236 : :
1237 [ + - ]: 2 : if (format == GST_FORMAT_TIME) {
1238 [ - + ]: 2 : GST_DEBUG_OBJECT (render, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1239 : : &render->video_segment);
1240 : :
1241 : 2 : gst_segment_set_newsegment (&render->video_segment, update, rate,
1242 : : format, start, stop, time);
1243 : :
1244 [ - + ]: 2 : GST_DEBUG_OBJECT (render, "VIDEO SEGMENT after: %" GST_SEGMENT_FORMAT,
1245 : : &render->video_segment);
1246 : 2 : ret = gst_pad_push_event (render->srcpad, event);
1247 : : } else {
1248 [ # # ][ # # ]: 0 : GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
[ # # ][ # # ]
1249 : : ("received non-TIME newsegment event on video input"));
1250 : 0 : ret = FALSE;
1251 : 0 : gst_event_unref (event);
1252 : : }
1253 : 2 : break;
1254 : : }
1255 : : case GST_EVENT_TAG:
1256 : : {
1257 : 0 : GstTagList *taglist = NULL;
1258 : :
1259 : : /* tag events may contain attachments which might be fonts */
1260 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "got TAG event");
1261 : :
1262 : 0 : gst_event_parse_tag (event, &taglist);
1263 : 0 : gst_ass_render_handle_tags (render, taglist);
1264 : 0 : ret = gst_pad_push_event (render->srcpad, event);
1265 : 0 : break;
1266 : : }
1267 : : case GST_EVENT_FLUSH_STOP:
1268 : 0 : gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
1269 : : default:
1270 : 2 : ret = gst_pad_push_event (render->srcpad, event);
1271 : 2 : break;
1272 : : }
1273 : :
1274 : 4 : gst_object_unref (render);
1275 : :
1276 : 4 : return ret;
1277 : : }
1278 : :
1279 : : static gboolean
1280 : 4 : gst_ass_render_event_text (GstPad * pad, GstEvent * event)
1281 : : {
1282 : : gint i;
1283 : 4 : gboolean ret = FALSE;
1284 : 4 : GstAssRender *render = GST_ASS_RENDER (gst_pad_get_parent (pad));
1285 : :
1286 [ - + ]: 4 : GST_DEBUG_OBJECT (pad, "received text event %s", GST_EVENT_TYPE_NAME (event));
1287 : :
1288 [ + - - + : 4 : switch (GST_EVENT_TYPE (event)) {
- - ]
1289 : : case GST_EVENT_NEWSEGMENT:
1290 : : {
1291 : : GstFormat format;
1292 : : gdouble rate;
1293 : : gint64 start, stop, time;
1294 : : gboolean update;
1295 : :
1296 [ - + ]: 2 : GST_DEBUG_OBJECT (render, "received new segment");
1297 : :
1298 : 2 : gst_event_parse_new_segment (event, &update, &rate, &format, &start,
1299 : : &stop, &time);
1300 : :
1301 [ + - ]: 2 : if (format == GST_FORMAT_TIME) {
1302 [ - + ]: 2 : GST_DEBUG_OBJECT (render, "SUBTITLE SEGMENT now: %" GST_SEGMENT_FORMAT,
1303 : : &render->subtitle_segment);
1304 : :
1305 : 2 : gst_segment_set_newsegment (&render->subtitle_segment, update, rate,
1306 : : format, start, stop, time);
1307 : :
1308 [ - + ]: 2 : GST_DEBUG_OBJECT (render,
1309 : : "SUBTITLE SEGMENT after: %" GST_SEGMENT_FORMAT,
1310 : : &render->subtitle_segment);
1311 : 2 : ret = TRUE;
1312 : 2 : gst_event_unref (event);
1313 : : } else {
1314 [ # # ][ # # ]: 0 : GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
[ # # ][ # # ]
1315 : : ("received non-TIME newsegment event on subtitle input"));
1316 : 0 : ret = FALSE;
1317 : 0 : gst_event_unref (event);
1318 : : }
1319 : 2 : break;
1320 : : }
1321 : : case GST_EVENT_FLUSH_STOP:
1322 : 0 : gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
1323 : 0 : render->subtitle_flushing = FALSE;
1324 : 0 : gst_event_unref (event);
1325 : 0 : ret = TRUE;
1326 : 0 : break;
1327 : : case GST_EVENT_FLUSH_START:
1328 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "begin flushing");
1329 : 0 : g_mutex_lock (render->ass_mutex);
1330 [ # # ]: 0 : if (render->ass_track) {
1331 : : /* delete any events on the ass_track */
1332 [ # # ]: 0 : for (i = 0; i < render->ass_track->n_events; i++) {
1333 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "deleted event with eid %i", i);
1334 : 0 : ass_free_event (render->ass_track, i);
1335 : : }
1336 : 0 : render->ass_track->n_events = 0;
1337 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "done flushing");
1338 : : }
1339 : 0 : g_mutex_unlock (render->ass_mutex);
1340 : 0 : g_mutex_lock (render->subtitle_mutex);
1341 [ # # ]: 0 : if (render->subtitle_pending)
1342 : 0 : gst_buffer_unref (render->subtitle_pending);
1343 : 0 : render->subtitle_pending = NULL;
1344 : 0 : render->subtitle_flushing = TRUE;
1345 : 0 : g_cond_signal (render->subtitle_cond);
1346 : 0 : g_mutex_unlock (render->subtitle_mutex);
1347 : 0 : gst_event_unref (event);
1348 : 0 : ret = TRUE;
1349 : 0 : break;
1350 : : case GST_EVENT_EOS:
1351 : 2 : GST_OBJECT_LOCK (render);
1352 [ - + ]: 2 : GST_INFO_OBJECT (render, "text EOS");
1353 : 2 : GST_OBJECT_UNLOCK (render);
1354 : 2 : gst_event_unref (event);
1355 : 2 : ret = TRUE;
1356 : 2 : break;
1357 : : case GST_EVENT_TAG:
1358 : : {
1359 : 0 : GstTagList *taglist = NULL;
1360 : :
1361 : : /* tag events may contain attachments which might be fonts */
1362 [ # # ]: 0 : GST_DEBUG_OBJECT (render, "got TAG event");
1363 : :
1364 : 0 : gst_event_parse_tag (event, &taglist);
1365 : 0 : gst_ass_render_handle_tags (render, taglist);
1366 : 0 : ret = gst_pad_push_event (render->srcpad, event);
1367 : 0 : break;
1368 : : }
1369 : : default:
1370 : 0 : ret = gst_pad_push_event (render->srcpad, event);
1371 : 0 : break;
1372 : : }
1373 : :
1374 : 4 : gst_object_unref (render);
1375 : :
1376 : 4 : return ret;
1377 : : }
1378 : :
1379 : : static gboolean
1380 : 8 : plugin_init (GstPlugin * plugin)
1381 : : {
1382 [ + - ]: 8 : GST_DEBUG_CATEGORY_INIT (gst_ass_render_debug, "assrender",
1383 : : 0, "ASS/SSA subtitle renderer");
1384 [ + - ]: 8 : GST_DEBUG_CATEGORY_INIT (gst_ass_render_lib_debug, "assrender_library",
1385 : : 0, "ASS/SSA subtitle renderer library");
1386 : :
1387 : 8 : return gst_element_register (plugin, "assrender",
1388 : : GST_RANK_PRIMARY, GST_TYPE_ASS_RENDER);
1389 : : }
1390 : :
1391 : : GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1392 : : GST_VERSION_MINOR,
1393 : : "assrender",
1394 : : "ASS/SSA subtitle renderer",
1395 : : plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|