1 /* GStreamer
2 * Copyright (C) 2015 Samsung Electronics Co., Ltd.
3 * @Author: Chengjun Wang <cjun.wang@samsung.com>
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., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <gst/video/video.h>
26 #include <gst/video/gstvideometa.h>
27 #include <gst/base/gstbytereader.h>
28
29 #include "gstceaccoverlay.h"
30 #include <string.h>
31
32
33 #define GST_CAT_DEFAULT gst_cea_cc_overlay_debug
34 GST_DEBUG_CATEGORY (gst_cea_cc_overlay_debug);
35
36
37 #define DEFAULT_PROP_FONT_DESC ""
38 #define DEFAULT_PROP_SILENT FALSE
39 #define DEFAULT_PROP_SERVICE_NUMBER 1
40 #define DEFAULT_PROP_WINDOW_H_POS GST_CEA_CC_OVERLAY_WIN_H_CENTER
41
42 enum
43 {
44 PROP_0,
45 PROP_FONT_DESC,
46 PROP_SILENT,
47 PROP_SERVICE_NUMBER,
48 PROP_WINDOW_H_POS,
49 PROP_LAST
50 };
51
52 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
53 # define CAIRO_ARGB_A 3
54 # define CAIRO_ARGB_R 2
55 # define CAIRO_ARGB_G 1
56 # define CAIRO_ARGB_B 0
57 #else
58 # define CAIRO_ARGB_A 0
59 # define CAIRO_ARGB_R 1
60 # define CAIRO_ARGB_G 2
61 # define CAIRO_ARGB_B 3
62 #endif
63
64 #define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \
65 b = (a > 0) ? MIN ((b * 255 + a / 2) / a, 255) : 0; \
66 g = (a > 0) ? MIN ((g * 255 + a / 2) / a, 255) : 0; \
67 r = (a > 0) ? MIN ((r * 255 + a / 2) / a, 255) : 0; \
68 } G_STMT_END
69
70
71 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
72
73 #define CC_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
74
75 #define CC_OVERLAY_ALL_CAPS CC_OVERLAY_CAPS ";" \
76 GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
77
78 static GstStaticCaps sw_template_caps = GST_STATIC_CAPS (CC_OVERLAY_CAPS);
79
80 static GstStaticPadTemplate src_template_factory =
81 GST_STATIC_PAD_TEMPLATE ("src",
82 GST_PAD_SRC,
83 GST_PAD_ALWAYS,
84 GST_STATIC_CAPS (CC_OVERLAY_ALL_CAPS)
85 );
86
87 static GstStaticPadTemplate video_sink_template_factory =
88 GST_STATIC_PAD_TEMPLATE ("video_sink",
89 GST_PAD_SINK,
90 GST_PAD_ALWAYS,
91 GST_STATIC_CAPS (CC_OVERLAY_ALL_CAPS)
92 );
93
94 static GstStaticPadTemplate cc_sink_template_factory =
95 GST_STATIC_PAD_TEMPLATE ("cc_sink",
96 GST_PAD_SINK,
97 GST_PAD_ALWAYS,
98 GST_STATIC_CAPS
99 ("closedcaption/x-cea-708, format={ (string) cdp, (string) cc_data }")
100 );
101
102
103 #define GST_TYPE_CC_OVERLAY_WIN_H_POS (gst_cea_cc_overlay_h_pos_get_type())
104 static GType
gst_cea_cc_overlay_h_pos_get_type(void)105 gst_cea_cc_overlay_h_pos_get_type (void)
106 {
107 static GType cc_overlay_win_h_pos_type = 0;
108 static const GEnumValue cc_overlay_win_h_pos[] = {
109 {GST_CEA_CC_OVERLAY_WIN_H_LEFT, "left", "left"},
110 {GST_CEA_CC_OVERLAY_WIN_H_CENTER, "center", "center"},
111 {GST_CEA_CC_OVERLAY_WIN_H_RIGHT, "right", "right"},
112 {GST_CEA_CC_OVERLAY_WIN_H_AUTO, "auto", "auto"},
113 {0, NULL, NULL},
114 };
115
116 if (!cc_overlay_win_h_pos_type) {
117 cc_overlay_win_h_pos_type =
118 g_enum_register_static ("GstCeaCcOverlayWinHPos", cc_overlay_win_h_pos);
119 }
120 return cc_overlay_win_h_pos_type;
121 }
122
123
124 #define GST_CEA_CC_OVERLAY_GET_LOCK(ov) (&GST_CEA_CC_OVERLAY (ov)->lock)
125 #define GST_CEA_CC_OVERLAY_GET_COND(ov) (&GST_CEA_CC_OVERLAY (ov)->cond)
126 #define GST_CEA_CC_OVERLAY_LOCK(ov) (g_mutex_lock (GST_CEA_CC_OVERLAY_GET_LOCK (ov)))
127 #define GST_CEA_CC_OVERLAY_UNLOCK(ov) (g_mutex_unlock (GST_CEA_CC_OVERLAY_GET_LOCK (ov)))
128 #define GST_CEA_CC_OVERLAY_WAIT(ov) (g_cond_wait (GST_CEA_CC_OVERLAY_GET_COND (ov), GST_CEA_CC_OVERLAY_GET_LOCK (ov)))
129 #define GST_CEA_CC_OVERLAY_SIGNAL(ov) (g_cond_signal (GST_CEA_CC_OVERLAY_GET_COND (ov)))
130 #define GST_CEA_CC_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_CEA_CC_OVERLAY_GET_COND (ov)))
131
132 static GstElementClass *parent_class = NULL;
133 static void gst_base_cea_cc_overlay_base_init (gpointer g_class);
134 static void gst_base_cea_cc_overlay_class_init (GstCeaCcOverlayClass * klass);
135 static void gst_base_cea_cc_overlay_init (GstCeaCcOverlay * overlay,
136 GstCeaCcOverlayClass * klass);
137 static GstStateChangeReturn gst_cea_cc_overlay_change_state (GstElement *
138 element, GstStateChange transition);
139 static GstCaps *gst_cea_cc_overlay_get_videosink_caps (GstPad * pad,
140 GstCeaCcOverlay * overlay, GstCaps * filter);
141 static GstCaps *gst_cea_cc_overlay_get_src_caps (GstPad * pad,
142 GstCeaCcOverlay * overlay, GstCaps * filter);
143 static gboolean gst_cea_cc_overlay_setcaps (GstCeaCcOverlay * overlay,
144 GstCaps * caps);
145 static gboolean gst_cea_cc_overlay_src_event (GstPad * pad, GstObject * parent,
146 GstEvent * event);
147 static gboolean gst_cea_cc_overlay_src_query (GstPad * pad, GstObject * parent,
148 GstQuery * query);
149
150 static gboolean gst_cea_cc_overlay_video_event (GstPad * pad,
151 GstObject * parent, GstEvent * event);
152 static gboolean gst_cea_cc_overlay_video_query (GstPad * pad,
153 GstObject * parent, GstQuery * query);
154 static GstFlowReturn gst_cea_cc_overlay_video_chain (GstPad * pad,
155 GstObject * parent, GstBuffer * buffer);
156
157 static gboolean gst_cea_cc_overlay_cc_event (GstPad * pad,
158 GstObject * parent, GstEvent * event);
159 static GstFlowReturn gst_cea_cc_overlay_cc_chain (GstPad * pad,
160 GstObject * parent, GstBuffer * buffer);
161 static GstPadLinkReturn gst_cea_cc_overlay_cc_pad_link (GstPad * pad,
162 GstObject * parent, GstPad * peer);
163 static void gst_cea_cc_overlay_cc_pad_unlink (GstPad * pad, GstObject * parent);
164 static void gst_cea_cc_overlay_pop_text (GstCeaCcOverlay * overlay);
165 static void gst_cea_cc_overlay_finalize (GObject * object);
166 static void gst_cea_cc_overlay_set_property (GObject * object, guint prop_id,
167 const GValue * value, GParamSpec * pspec);
168 static void gst_cea_cc_overlay_get_property (GObject * object, guint prop_id,
169 GValue * value, GParamSpec * pspec);
170
171 static gboolean gst_cea_cc_overlay_can_handle_caps (GstCaps * incaps);
172
173 GType
gst_cea_cc_overlay_get_type(void)174 gst_cea_cc_overlay_get_type (void)
175 {
176 static GType type = 0;
177
178 if (g_once_init_enter ((gsize *) & type)) {
179 static const GTypeInfo info = {
180 sizeof (GstCeaCcOverlayClass),
181 (GBaseInitFunc) gst_base_cea_cc_overlay_base_init,
182 NULL,
183 (GClassInitFunc) gst_base_cea_cc_overlay_class_init,
184 NULL,
185 NULL,
186 sizeof (GstCeaCcOverlay),
187 0,
188 (GInstanceInitFunc) gst_base_cea_cc_overlay_init,
189 };
190
191 g_once_init_leave ((gsize *) & type,
192 g_type_register_static (GST_TYPE_ELEMENT, "GstCeaCcOverlay", &info, 0));
193 }
194
195 return type;
196 }
197
198 static void
gst_base_cea_cc_overlay_base_init(gpointer g_class)199 gst_base_cea_cc_overlay_base_init (gpointer g_class)
200 {
201 GstCeaCcOverlayClass *klass = GST_CEA_CC_OVERLAY_CLASS (g_class);
202 PangoFontMap *fontmap;
203
204 /* Only lock for the subclasses here, the base class
205 * doesn't have this mutex yet and it's not necessary
206 * here */
207 /* FIXME : Not needed anymore since pango 1.32.6 ! */
208 if (klass->pango_lock)
209 g_mutex_lock (klass->pango_lock);
210 fontmap = pango_cairo_font_map_get_default ();
211 klass->pango_context =
212 pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
213 if (klass->pango_lock)
214 g_mutex_unlock (klass->pango_lock);
215
216 }
217
218 static void
gst_base_cea_cc_overlay_class_init(GstCeaCcOverlayClass * klass)219 gst_base_cea_cc_overlay_class_init (GstCeaCcOverlayClass * klass)
220 {
221 GObjectClass *gobject_class;
222 GstElementClass *gstelement_class;
223
224 gobject_class = (GObjectClass *) klass;
225 gstelement_class = (GstElementClass *) klass;
226
227 GST_DEBUG_CATEGORY_INIT (gst_cea_cc_overlay_debug, "cc708overlay", 0,
228 "cc708overlay");
229
230 parent_class = g_type_class_peek_parent (klass);
231
232 gobject_class->finalize = gst_cea_cc_overlay_finalize;
233 gobject_class->set_property = gst_cea_cc_overlay_set_property;
234 gobject_class->get_property = gst_cea_cc_overlay_get_property;
235
236 gst_element_class_add_pad_template (gstelement_class,
237 gst_static_pad_template_get (&src_template_factory));
238 gst_element_class_add_pad_template (gstelement_class,
239 gst_static_pad_template_get (&video_sink_template_factory));
240 gst_element_class_add_pad_template (gstelement_class,
241 gst_static_pad_template_get (&cc_sink_template_factory));
242
243 gstelement_class->change_state =
244 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_change_state);
245
246 klass->pango_lock = g_slice_new (GMutex);
247 g_mutex_init (klass->pango_lock);
248
249 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SERVICE_NUMBER,
250 g_param_spec_int ("service-number", "service-number",
251 "Service number. Service 1 is designated as the Primary Caption Service,"
252 " Service 2 is the Secondary Language Service.",
253 -1, 63, DEFAULT_PROP_SERVICE_NUMBER,
254 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
255
256 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WINDOW_H_POS,
257 g_param_spec_enum ("window-h-pos", "window-h-pos",
258 "Window's Horizontal position", GST_TYPE_CC_OVERLAY_WIN_H_POS,
259 DEFAULT_PROP_WINDOW_H_POS,
260 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
261
262 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
263 g_param_spec_string ("font-desc", "font description",
264 "Pango font description of font to be used for rendering.\n"
265 "See documentation of pango_font_description_from_string for syntax.\n"
266 "this will override closed caption stream specified font style/pen size.",
267 DEFAULT_PROP_FONT_DESC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
268
269 /**
270 * GstCeaCcOverlay:silent:
271 *
272 * If set, no text is rendered. Useful to switch off text rendering
273 * temporarily without removing the textoverlay element from the pipeline.
274 */
275 /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
276 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
277 g_param_spec_boolean ("silent", "silent",
278 "Whether to render the text string",
279 DEFAULT_PROP_SILENT,
280 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
281
282 gst_element_class_set_static_metadata (gstelement_class,
283 "Closed Caption overlay", "Mixer/Video/Overlay/Subtitle",
284 "Decode cea608/cea708 data and overlay on proper position of a video buffer",
285 "Chengjun Wang <cjun.wang@samsung.com>");
286 gst_cea708_decoder_init_debug ();
287
288 }
289
290 static void
gst_cea_cc_overlay_finalize(GObject * object)291 gst_cea_cc_overlay_finalize (GObject * object)
292 {
293 GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object);
294
295 if (overlay->current_composition) {
296 gst_video_overlay_composition_unref (overlay->current_composition);
297 overlay->current_composition = NULL;
298 }
299 if (overlay->next_composition) {
300 gst_video_overlay_composition_unref (overlay->next_composition);
301 overlay->next_composition = NULL;
302 }
303
304 gst_cea708dec_free (overlay->decoder);
305 overlay->decoder = NULL;
306
307 g_mutex_clear (&overlay->lock);
308 g_cond_clear (&overlay->cond);
309
310 G_OBJECT_CLASS (parent_class)->finalize (object);
311 }
312
313 static void
gst_base_cea_cc_overlay_init(GstCeaCcOverlay * overlay,GstCeaCcOverlayClass * klass)314 gst_base_cea_cc_overlay_init (GstCeaCcOverlay * overlay,
315 GstCeaCcOverlayClass * klass)
316 {
317 GstPadTemplate *template;
318 overlay->decoder = gst_cea708dec_create (GST_CEA_CC_OVERLAY_GET_CLASS
319 (overlay)->pango_context);
320
321 /* video sink */
322 template = gst_static_pad_template_get (&video_sink_template_factory);
323 overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
324 gst_object_unref (template);
325 gst_pad_set_event_function (overlay->video_sinkpad,
326 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_event));
327 gst_pad_set_chain_function (overlay->video_sinkpad,
328 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_chain));
329 gst_pad_set_query_function (overlay->video_sinkpad,
330 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_query));
331 GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
332 gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
333
334 template =
335 gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "cc_sink");
336 if (template) {
337 /* text sink */
338 overlay->cc_sinkpad = gst_pad_new_from_template (template, "cc_sink");
339
340 gst_pad_set_event_function (overlay->cc_sinkpad,
341 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_event));
342 gst_pad_set_chain_function (overlay->cc_sinkpad,
343 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_chain));
344 gst_pad_set_link_function (overlay->cc_sinkpad,
345 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_pad_link));
346 gst_pad_set_unlink_function (overlay->cc_sinkpad,
347 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_pad_unlink));
348 gst_element_add_pad (GST_ELEMENT (overlay), overlay->cc_sinkpad);
349 }
350
351 /* (video) source */
352 template = gst_static_pad_template_get (&src_template_factory);
353 overlay->srcpad = gst_pad_new_from_template (template, "src");
354 gst_object_unref (template);
355 gst_pad_set_event_function (overlay->srcpad,
356 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_src_event));
357 gst_pad_set_query_function (overlay->srcpad,
358 GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_src_query));
359 gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
360
361
362 overlay->silent = DEFAULT_PROP_SILENT;
363 overlay->need_update = TRUE;
364 overlay->current_composition = NULL;
365 overlay->next_composition = NULL;
366 overlay->cc_pad_linked = FALSE;
367 overlay->current_comp_start_time = GST_CLOCK_TIME_NONE;
368 overlay->next_comp_start_time = GST_CLOCK_TIME_NONE;
369 overlay->cea608_index[0] = 0;
370 overlay->cea608_index[1] = 0;
371 overlay->cea708_index = 0;
372 overlay->default_window_h_pos = DEFAULT_PROP_WINDOW_H_POS;
373
374 g_mutex_init (&overlay->lock);
375 g_cond_init (&overlay->cond);
376 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
377 }
378
379 /* only negotiate/query video overlay composition support for now */
380 static gboolean
gst_cea_cc_overlay_negotiate(GstCeaCcOverlay * overlay,GstCaps * caps)381 gst_cea_cc_overlay_negotiate (GstCeaCcOverlay * overlay, GstCaps * caps)
382 {
383 GstQuery *query;
384 gboolean attach = FALSE;
385 gboolean caps_has_meta = TRUE;
386 gboolean ret;
387 GstCapsFeatures *f;
388 GstCaps *original_caps;
389 gboolean original_has_meta = FALSE;
390 gboolean allocation_ret = TRUE;
391
392 GST_DEBUG_OBJECT (overlay, "performing negotiation");
393
394 if (!caps)
395 caps = gst_pad_get_current_caps (overlay->video_sinkpad);
396 else
397 gst_caps_ref (caps);
398
399 if (!caps || gst_caps_is_empty (caps))
400 goto no_format;
401
402 original_caps = caps;
403
404 /* Try to use the overlay meta if possible */
405 f = gst_caps_get_features (caps, 0);
406
407 /* if the caps doesn't have the overlay meta, we query if downstream
408 * accepts it before trying the version without the meta
409 * If upstream already is using the meta then we can only use it */
410 if (!f
411 || !gst_caps_features_contains (f,
412 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) {
413 GstCaps *overlay_caps;
414
415 /* In this case we added the meta, but we can work without it
416 * so preserve the original caps so we can use it as a fallback */
417 overlay_caps = gst_caps_copy (caps);
418
419 f = gst_caps_get_features (overlay_caps, 0);
420 gst_caps_features_add (f,
421 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
422
423 ret = gst_pad_peer_query_accept_caps (overlay->srcpad, overlay_caps);
424 GST_DEBUG_OBJECT (overlay, "Downstream accepts the overlay meta: %d", ret);
425 if (ret) {
426 gst_caps_unref (caps);
427 caps = overlay_caps;
428
429 } else {
430 /* fallback to the original */
431 gst_caps_unref (overlay_caps);
432 caps_has_meta = FALSE;
433 }
434 } else {
435 original_has_meta = TRUE;
436 }
437 GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps);
438 ret = gst_pad_set_caps (overlay->srcpad, caps);
439
440 if (ret) {
441 /* find supported meta */
442 query = gst_query_new_allocation (caps, FALSE);
443
444 if (!gst_pad_peer_query (overlay->srcpad, query)) {
445 /* no problem, we use the query defaults */
446 GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
447 allocation_ret = FALSE;
448 }
449
450 if (caps_has_meta && gst_query_find_allocation_meta (query,
451 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
452 attach = TRUE;
453 gst_query_unref (query);
454 }
455
456 overlay->attach_compo_to_buffer = attach;
457
458 if (!allocation_ret && overlay->video_flushing) {
459 ret = FALSE;
460 } else if (original_caps && !original_has_meta && !attach) {
461 if (caps_has_meta) {
462 /* Some elements (fakesink) claim to accept the meta on caps but won't
463 put it in the allocation query result, this leads below
464 check to fail. Prevent this by removing the meta from caps */
465 gst_caps_unref (caps);
466 caps = gst_caps_ref (original_caps);
467 ret = gst_pad_set_caps (overlay->srcpad, caps);
468 if (ret && !gst_cea_cc_overlay_can_handle_caps (caps))
469 ret = FALSE;
470 }
471 }
472
473 if (!ret) {
474 GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure");
475 gst_pad_mark_reconfigure (overlay->srcpad);
476 }
477 gst_caps_unref (caps);
478 GST_DEBUG_OBJECT (overlay, "ret=%d", ret);
479
480 return ret;
481
482 no_format:
483 {
484 if (caps)
485 gst_caps_unref (caps);
486 return FALSE;
487 }
488 }
489
490 static gboolean
gst_cea_cc_overlay_can_handle_caps(GstCaps * incaps)491 gst_cea_cc_overlay_can_handle_caps (GstCaps * incaps)
492 {
493 gboolean ret;
494 GstCaps *caps;
495 static GstStaticCaps static_caps = GST_STATIC_CAPS (CC_OVERLAY_CAPS);
496
497 caps = gst_static_caps_get (&static_caps);
498 ret = gst_caps_is_subset (incaps, caps);
499 gst_caps_unref (caps);
500
501 return ret;
502 }
503
504 static gboolean
gst_cea_cc_overlay_setcaps(GstCeaCcOverlay * overlay,GstCaps * caps)505 gst_cea_cc_overlay_setcaps (GstCeaCcOverlay * overlay, GstCaps * caps)
506 {
507 GstVideoInfo info;
508 gboolean ret = FALSE;
509
510 if (!gst_video_info_from_caps (&info, caps))
511 goto invalid_caps;
512
513 overlay->info = info;
514 overlay->format = GST_VIDEO_INFO_FORMAT (&info);
515 overlay->width = GST_VIDEO_INFO_WIDTH (&info);
516 overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
517 gst_cea708dec_set_video_width_height (overlay->decoder, overlay->width,
518 overlay->height);
519 ret = gst_cea_cc_overlay_negotiate (overlay, caps);
520
521 GST_CEA_CC_OVERLAY_LOCK (overlay);
522 g_mutex_lock (GST_CEA_CC_OVERLAY_GET_CLASS (overlay)->pango_lock);
523 if (!overlay->attach_compo_to_buffer &&
524 !gst_cea_cc_overlay_can_handle_caps (caps)) {
525 GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps);
526 ret = FALSE;
527 }
528
529 g_mutex_unlock (GST_CEA_CC_OVERLAY_GET_CLASS (overlay)->pango_lock);
530 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
531
532 return ret;
533
534 /* ERRORS */
535 invalid_caps:
536 {
537 GST_DEBUG_OBJECT (overlay, "could not parse caps");
538 return FALSE;
539 }
540 }
541
542 static void
gst_cea_cc_overlay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)543 gst_cea_cc_overlay_set_property (GObject * object, guint prop_id,
544 const GValue * value, GParamSpec * pspec)
545 {
546 GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object);
547 Cea708Dec *decoder = overlay->decoder;
548
549 GST_CEA_CC_OVERLAY_LOCK (overlay);
550 switch (prop_id) {
551 case PROP_SERVICE_NUMBER:
552 {
553 int desired_service = g_value_get_int (value);
554 gst_cea708dec_set_service_number (decoder, desired_service);
555 break;
556 }
557 case PROP_FONT_DESC:
558 {
559 PangoFontDescription *desc = NULL;
560 const gchar *fontdesc_str;
561 fontdesc_str = g_value_get_string (value);
562
563 GST_LOG_OBJECT (overlay, "Got font description '%s'", fontdesc_str);
564 if (fontdesc_str)
565 desc = pango_font_description_from_string (fontdesc_str);
566 /* Only set if NULL or valid description */
567 if (desc || !fontdesc_str) {
568 if (desc) {
569 GST_INFO_OBJECT (overlay, "Setting font description: '%s'",
570 fontdesc_str);
571 pango_font_description_free (desc);
572 } else
573 GST_INFO_OBJECT (overlay, "Resetting default font description");
574 g_free (decoder->default_font_desc);
575 decoder->default_font_desc = g_strdup (fontdesc_str);
576 }
577 break;
578 }
579 case PROP_SILENT:
580 overlay->silent = g_value_get_boolean (value);
581 break;
582 case PROP_WINDOW_H_POS:
583 overlay->default_window_h_pos = g_value_get_enum (value);
584 break;
585 default:
586 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
587 break;
588 }
589
590 overlay->need_update = TRUE;
591 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
592 }
593
594 static void
gst_cea_cc_overlay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)595 gst_cea_cc_overlay_get_property (GObject * object, guint prop_id,
596 GValue * value, GParamSpec * pspec)
597 {
598 GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object);
599 Cea708Dec *decoder = overlay->decoder;
600
601 GST_CEA_CC_OVERLAY_LOCK (overlay);
602 switch (prop_id) {
603 case PROP_SERVICE_NUMBER:
604 g_value_set_int (value, decoder->desired_service);
605 break;
606 case PROP_SILENT:
607 g_value_set_boolean (value, overlay->silent);
608 break;
609 case PROP_FONT_DESC:
610 g_value_set_string (value, decoder->default_font_desc);
611 break;
612 case PROP_WINDOW_H_POS:
613 g_value_set_enum (value, overlay->default_window_h_pos);
614 break;
615 default:
616 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
617 break;
618 }
619
620 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
621 }
622
623 static gboolean
gst_cea_cc_overlay_src_query(GstPad * pad,GstObject * parent,GstQuery * query)624 gst_cea_cc_overlay_src_query (GstPad * pad, GstObject * parent,
625 GstQuery * query)
626 {
627 gboolean ret = FALSE;
628 GstCeaCcOverlay *overlay;
629
630 overlay = GST_CEA_CC_OVERLAY (parent);
631
632 switch (GST_QUERY_TYPE (query)) {
633 case GST_QUERY_CAPS:
634 {
635 GstCaps *filter, *caps;
636
637 gst_query_parse_caps (query, &filter);
638 caps = gst_cea_cc_overlay_get_src_caps (pad, overlay, filter);
639 gst_query_set_caps_result (query, caps);
640 gst_caps_unref (caps);
641 ret = TRUE;
642 break;
643 }
644 default:
645 ret = gst_pad_query_default (pad, parent, query);
646 break;
647 }
648
649 return ret;
650 }
651
652 static gboolean
gst_cea_cc_overlay_src_event(GstPad * pad,GstObject * parent,GstEvent * event)653 gst_cea_cc_overlay_src_event (GstPad * pad, GstObject * parent,
654 GstEvent * event)
655 {
656 GstCeaCcOverlay *overlay;
657 gboolean ret;
658
659 overlay = GST_CEA_CC_OVERLAY (parent);
660
661 if (overlay->cc_pad_linked) {
662 gst_event_ref (event);
663 ret = gst_pad_push_event (overlay->video_sinkpad, event);
664 gst_pad_push_event (overlay->cc_sinkpad, event);
665 } else {
666 ret = gst_pad_push_event (overlay->video_sinkpad, event);
667 }
668
669 return ret;
670 }
671
672 /**
673 * gst_cea_cc_overlay_add_feature_and_intersect:
674 *
675 * Creates a new #GstCaps containing the (given caps +
676 * given caps feature) + (given caps intersected by the
677 * given filter).
678 *
679 * Returns: the new #GstCaps
680 */
681 static GstCaps *
gst_cea_cc_overlay_add_feature_and_intersect(GstCaps * caps,const gchar * feature,GstCaps * filter)682 gst_cea_cc_overlay_add_feature_and_intersect (GstCaps * caps,
683 const gchar * feature, GstCaps * filter)
684 {
685 int i, caps_size;
686 GstCaps *new_caps;
687
688 new_caps = gst_caps_copy (caps);
689
690 caps_size = gst_caps_get_size (new_caps);
691 for (i = 0; i < caps_size; i++) {
692 GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
693
694 if (!gst_caps_features_is_any (features)) {
695 gst_caps_features_add (features, feature);
696 }
697 }
698
699 gst_caps_append (new_caps, gst_caps_intersect_full (caps,
700 filter, GST_CAPS_INTERSECT_FIRST));
701
702 return new_caps;
703 }
704
705 /**
706 * gst_cea_cc_overlay_intersect_by_feature:
707 *
708 * Creates a new #GstCaps based on the following filtering rule.
709 *
710 * For each individual caps contained in given caps, if the
711 * caps uses the given caps feature, keep a version of the caps
712 * with the feature and an another one without. Otherwise, intersect
713 * the caps with the given filter.
714 *
715 * Returns: the new #GstCaps
716 */
717 static GstCaps *
gst_cea_cc_overlay_intersect_by_feature(GstCaps * caps,const gchar * feature,GstCaps * filter)718 gst_cea_cc_overlay_intersect_by_feature (GstCaps * caps,
719 const gchar * feature, GstCaps * filter)
720 {
721 int i, caps_size;
722 GstCaps *new_caps;
723
724 new_caps = gst_caps_new_empty ();
725
726 caps_size = gst_caps_get_size (caps);
727 for (i = 0; i < caps_size; i++) {
728 GstStructure *caps_structure = gst_caps_get_structure (caps, i);
729 GstCapsFeatures *caps_features =
730 gst_caps_features_copy (gst_caps_get_features (caps, i));
731 GstCaps *filtered_caps;
732 GstCaps *simple_caps =
733 gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
734 gst_caps_set_features (simple_caps, 0, caps_features);
735
736 if (gst_caps_features_contains (caps_features, feature)) {
737 gst_caps_append (new_caps, gst_caps_copy (simple_caps));
738
739 gst_caps_features_remove (caps_features, feature);
740 filtered_caps = gst_caps_ref (simple_caps);
741 } else {
742 filtered_caps = gst_caps_intersect_full (simple_caps, filter,
743 GST_CAPS_INTERSECT_FIRST);
744 }
745 gst_caps_unref (simple_caps);
746 gst_caps_append (new_caps, filtered_caps);
747 }
748
749 return new_caps;
750 }
751
752 static GstCaps *
gst_cea_cc_overlay_get_videosink_caps(GstPad * pad,GstCeaCcOverlay * overlay,GstCaps * filter)753 gst_cea_cc_overlay_get_videosink_caps (GstPad * pad,
754 GstCeaCcOverlay * overlay, GstCaps * filter)
755 {
756 GstPad *srcpad = overlay->srcpad;
757 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
758
759 if (G_UNLIKELY (!overlay))
760 return gst_pad_get_pad_template_caps (pad);
761
762 if (filter) {
763 /* filter caps + composition feature + filter caps
764 * filtered by the software caps. */
765 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
766 overlay_filter = gst_cea_cc_overlay_add_feature_and_intersect (filter,
767 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
768 gst_caps_unref (sw_caps);
769
770 GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
771 overlay_filter);
772 }
773
774 peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
775 if (overlay_filter)
776 gst_caps_unref (overlay_filter);
777 if (peer_caps) {
778
779 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
780
781 if (gst_caps_is_any (peer_caps)) {
782 /* if peer returns ANY caps, return filtered src pad template caps */
783 caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
784 } else {
785
786 /* duplicate caps which contains the composition into one version with
787 * the meta and one without. Filter the other caps by the software caps */
788 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
789 caps = gst_cea_cc_overlay_intersect_by_feature (peer_caps,
790 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
791 gst_caps_unref (sw_caps);
792 }
793
794 gst_caps_unref (peer_caps);
795
796 } else {
797 /* no peer, our padtemplate is enough then */
798 caps = gst_pad_get_pad_template_caps (pad);
799 }
800
801 if (filter) {
802 GstCaps *intersection = gst_caps_intersect_full (filter, caps,
803 GST_CAPS_INTERSECT_FIRST);
804 gst_caps_unref (caps);
805 caps = intersection;
806 }
807
808 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
809
810 return caps;
811 }
812
813 static GstCaps *
gst_cea_cc_overlay_get_src_caps(GstPad * pad,GstCeaCcOverlay * overlay,GstCaps * filter)814 gst_cea_cc_overlay_get_src_caps (GstPad * pad, GstCeaCcOverlay * overlay,
815 GstCaps * filter)
816 {
817 GstPad *sinkpad = overlay->video_sinkpad;
818 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
819
820 if (G_UNLIKELY (!overlay))
821 return gst_pad_get_pad_template_caps (pad);
822
823 if (filter) {
824 /* duplicate filter caps which contains the composition into one version
825 * with the meta and one without. Filter the other caps by the software
826 * caps */
827 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
828 overlay_filter =
829 gst_cea_cc_overlay_intersect_by_feature (filter,
830 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
831 gst_caps_unref (sw_caps);
832 }
833
834 peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
835
836 if (overlay_filter)
837 gst_caps_unref (overlay_filter);
838
839 if (peer_caps) {
840
841 GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
842
843 if (gst_caps_is_any (peer_caps)) {
844
845 /* if peer returns ANY caps, return filtered sink pad template caps */
846 caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
847
848 } else {
849
850 /* return upstream caps + composition feature + upstream caps
851 * filtered by the software caps. */
852 GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
853 caps = gst_cea_cc_overlay_add_feature_and_intersect (peer_caps,
854 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
855 gst_caps_unref (sw_caps);
856 }
857
858 gst_caps_unref (peer_caps);
859
860 } else {
861 /* no peer, our padtemplate is enough then */
862 caps = gst_pad_get_pad_template_caps (pad);
863 }
864
865 if (filter) {
866 GstCaps *intersection;
867
868 intersection =
869 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
870 gst_caps_unref (caps);
871 caps = intersection;
872 }
873 GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
874
875 return caps;
876 }
877
878 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
879 #define BOX_XPAD 6
880 #define BOX_YPAD 6
881
882 static GstFlowReturn
gst_cea_cc_overlay_push_frame(GstCeaCcOverlay * overlay,GstBuffer * video_frame)883 gst_cea_cc_overlay_push_frame (GstCeaCcOverlay * overlay,
884 GstBuffer * video_frame)
885 {
886 GstVideoFrame frame;
887
888 if (overlay->current_composition == NULL)
889 goto done;
890 GST_LOG_OBJECT (overlay, "gst_cea_cc_overlay_push_frame");
891
892 if (gst_pad_check_reconfigure (overlay->srcpad))
893 gst_cea_cc_overlay_negotiate (overlay, NULL);
894
895 video_frame = gst_buffer_make_writable (video_frame);
896
897 if (overlay->attach_compo_to_buffer) {
898 GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
899 gst_buffer_add_video_overlay_composition_meta (video_frame,
900 overlay->current_composition);
901 goto done;
902 }
903
904 if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
905 GST_MAP_READWRITE))
906 goto invalid_frame;
907
908 gst_video_overlay_composition_blend (overlay->current_composition, &frame);
909
910 gst_video_frame_unmap (&frame);
911
912 done:
913
914 return gst_pad_push (overlay->srcpad, video_frame);
915
916 /* ERRORS */
917 invalid_frame:
918 {
919 gst_buffer_unref (video_frame);
920 return GST_FLOW_OK;
921 }
922 }
923
924 static GstPadLinkReturn
gst_cea_cc_overlay_cc_pad_link(GstPad * pad,GstObject * parent,GstPad * peer)925 gst_cea_cc_overlay_cc_pad_link (GstPad * pad, GstObject * parent, GstPad * peer)
926 {
927 GstCeaCcOverlay *overlay;
928
929 overlay = GST_CEA_CC_OVERLAY (parent);
930 if (G_UNLIKELY (!overlay))
931 return GST_PAD_LINK_REFUSED;
932
933 GST_DEBUG_OBJECT (overlay, "Closed Caption pad linked");
934
935 overlay->cc_pad_linked = TRUE;
936
937 return GST_PAD_LINK_OK;
938 }
939
940 static void
gst_cea_cc_overlay_cc_pad_unlink(GstPad * pad,GstObject * parent)941 gst_cea_cc_overlay_cc_pad_unlink (GstPad * pad, GstObject * parent)
942 {
943 GstCeaCcOverlay *overlay;
944
945 /* don't use gst_pad_get_parent() here, will deadlock */
946 overlay = GST_CEA_CC_OVERLAY (parent);
947
948 GST_DEBUG_OBJECT (overlay, "Closed Caption pad unlinked");
949
950 overlay->cc_pad_linked = FALSE;
951
952 gst_segment_init (&overlay->cc_segment, GST_FORMAT_UNDEFINED);
953 }
954
955 static gboolean
gst_cea_cc_overlay_cc_event(GstPad * pad,GstObject * parent,GstEvent * event)956 gst_cea_cc_overlay_cc_event (GstPad * pad, GstObject * parent, GstEvent * event)
957 {
958 gboolean ret = FALSE;
959 GstCeaCcOverlay *overlay = NULL;
960
961 overlay = GST_CEA_CC_OVERLAY (parent);
962
963 GST_LOG_OBJECT (overlay, "received event %s", GST_EVENT_TYPE_NAME (event));
964
965 switch (GST_EVENT_TYPE (event)) {
966 case GST_EVENT_CAPS:
967 {
968 GstCaps *caps;
969 GstStructure *st;
970 const gchar *cctype;
971
972 gst_event_parse_caps (event, &caps);
973 st = gst_caps_get_structure (caps, 0);
974 cctype = gst_structure_get_string (st, "format");
975 overlay->is_cdp = !g_strcmp0 (cctype, "cdp");
976 ret = TRUE;
977 break;
978 }
979 case GST_EVENT_SEGMENT:
980 {
981 const GstSegment *segment;
982
983 overlay->cc_eos = FALSE;
984
985 gst_event_parse_segment (event, &segment);
986
987 if (segment->format == GST_FORMAT_TIME) {
988 GST_CEA_CC_OVERLAY_LOCK (overlay);
989 gst_segment_copy_into (segment, &overlay->cc_segment);
990 GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
991 &overlay->cc_segment);
992 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
993 } else {
994 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
995 ("received non-TIME newsegment event on text input"));
996 }
997
998 gst_event_unref (event);
999 ret = TRUE;
1000
1001 /* wake up the video chain, it might be waiting for a text buffer or
1002 * a text segment update */
1003 GST_CEA_CC_OVERLAY_LOCK (overlay);
1004 GST_CEA_CC_OVERLAY_BROADCAST (overlay);
1005 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1006 break;
1007 }
1008 case GST_EVENT_GAP:
1009 {
1010 GstClockTime start, duration;
1011
1012 gst_event_parse_gap (event, &start, &duration);
1013 if (GST_CLOCK_TIME_IS_VALID (duration))
1014 start += duration;
1015 /* we do not expect another buffer until after gap,
1016 * so that is our position now */
1017 overlay->cc_segment.position = start;
1018
1019 /* wake up the video chain, it might be waiting for a text buffer or
1020 * a text segment update */
1021 GST_CEA_CC_OVERLAY_LOCK (overlay);
1022 GST_CEA_CC_OVERLAY_BROADCAST (overlay);
1023 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1024
1025 gst_event_unref (event);
1026 ret = TRUE;
1027 break;
1028 }
1029 case GST_EVENT_FLUSH_STOP:
1030 GST_CEA_CC_OVERLAY_LOCK (overlay);
1031 GST_INFO_OBJECT (overlay, "text flush stop");
1032 overlay->cc_flushing = FALSE;
1033 overlay->cc_eos = FALSE;
1034 gst_cea_cc_overlay_pop_text (overlay);
1035 gst_segment_init (&overlay->cc_segment, GST_FORMAT_TIME);
1036 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1037 gst_event_unref (event);
1038 ret = TRUE;
1039 break;
1040 case GST_EVENT_FLUSH_START:
1041 GST_CEA_CC_OVERLAY_LOCK (overlay);
1042 GST_INFO_OBJECT (overlay, "text flush start");
1043 overlay->cc_flushing = TRUE;
1044 GST_CEA_CC_OVERLAY_BROADCAST (overlay);
1045 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1046 gst_event_unref (event);
1047 ret = TRUE;
1048 break;
1049 case GST_EVENT_EOS:
1050 GST_CEA_CC_OVERLAY_LOCK (overlay);
1051 overlay->cc_eos = TRUE;
1052 GST_INFO_OBJECT (overlay, "closed caption EOS");
1053 /* wake up the video chain, it might be waiting for a text buffer or
1054 * a text segment update */
1055 GST_CEA_CC_OVERLAY_BROADCAST (overlay);
1056 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1057 gst_event_unref (event);
1058 ret = TRUE;
1059 break;
1060 default:
1061 ret = gst_pad_event_default (pad, parent, event);
1062 break;
1063 }
1064
1065 return ret;
1066 }
1067
1068 static gboolean
gst_cea_cc_overlay_video_event(GstPad * pad,GstObject * parent,GstEvent * event)1069 gst_cea_cc_overlay_video_event (GstPad * pad, GstObject * parent,
1070 GstEvent * event)
1071 {
1072 gboolean ret = FALSE;
1073 GstCeaCcOverlay *overlay = NULL;
1074
1075 overlay = GST_CEA_CC_OVERLAY (parent);
1076
1077 GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1078
1079 switch (GST_EVENT_TYPE (event)) {
1080 case GST_EVENT_CAPS:
1081 {
1082 GstCaps *caps;
1083
1084 gst_event_parse_caps (event, &caps);
1085 ret = gst_cea_cc_overlay_setcaps (overlay, caps);
1086 gst_event_unref (event);
1087 break;
1088 }
1089 case GST_EVENT_SEGMENT:
1090 {
1091 const GstSegment *segment;
1092
1093 GST_DEBUG_OBJECT (overlay, "received new segment");
1094
1095 gst_event_parse_segment (event, &segment);
1096
1097 if (segment->format == GST_FORMAT_TIME) {
1098 GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1099 &overlay->segment);
1100
1101 gst_segment_copy_into (segment, &overlay->segment);
1102 } else {
1103 GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1104 ("received non-TIME newsegment event on video input"));
1105 }
1106
1107 ret = gst_pad_event_default (pad, parent, event);
1108 break;
1109 }
1110 case GST_EVENT_EOS:
1111 GST_CEA_CC_OVERLAY_LOCK (overlay);
1112 GST_INFO_OBJECT (overlay, "video EOS");
1113 overlay->video_eos = TRUE;
1114 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1115 ret = gst_pad_event_default (pad, parent, event);
1116 break;
1117 case GST_EVENT_FLUSH_START:
1118 GST_CEA_CC_OVERLAY_LOCK (overlay);
1119 GST_INFO_OBJECT (overlay, "video flush start");
1120 overlay->video_flushing = TRUE;
1121 GST_CEA_CC_OVERLAY_BROADCAST (overlay);
1122 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1123 ret = gst_pad_event_default (pad, parent, event);
1124 break;
1125 case GST_EVENT_FLUSH_STOP:
1126 GST_CEA_CC_OVERLAY_LOCK (overlay);
1127 GST_INFO_OBJECT (overlay, "video flush stop");
1128 overlay->video_flushing = FALSE;
1129 overlay->video_eos = FALSE;
1130 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1131 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1132 ret = gst_pad_event_default (pad, parent, event);
1133 break;
1134 default:
1135 ret = gst_pad_event_default (pad, parent, event);
1136 break;
1137 }
1138
1139 return ret;
1140 }
1141
1142 static gboolean
gst_cea_cc_overlay_video_query(GstPad * pad,GstObject * parent,GstQuery * query)1143 gst_cea_cc_overlay_video_query (GstPad * pad, GstObject * parent,
1144 GstQuery * query)
1145 {
1146 gboolean ret = FALSE;
1147 GstCeaCcOverlay *overlay;
1148
1149 overlay = GST_CEA_CC_OVERLAY (parent);
1150
1151 switch (GST_QUERY_TYPE (query)) {
1152 case GST_QUERY_CAPS:
1153 {
1154 GstCaps *filter, *caps;
1155
1156 gst_query_parse_caps (query, &filter);
1157 caps = gst_cea_cc_overlay_get_videosink_caps (pad, overlay, filter);
1158 gst_query_set_caps_result (query, caps);
1159 gst_caps_unref (caps);
1160 ret = TRUE;
1161 break;
1162 }
1163 default:
1164 ret = gst_pad_query_default (pad, parent, query);
1165 break;
1166 }
1167
1168 return ret;
1169 }
1170
1171 /* Called with lock held */
1172 static void
gst_cea_cc_overlay_pop_text(GstCeaCcOverlay * overlay)1173 gst_cea_cc_overlay_pop_text (GstCeaCcOverlay * overlay)
1174 {
1175 g_return_if_fail (GST_IS_CEA_CC_OVERLAY (overlay));
1176
1177 if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)
1178 && overlay->current_composition) {
1179 GST_DEBUG_OBJECT (overlay, "releasing composition %p",
1180 overlay->current_composition);
1181 gst_video_overlay_composition_unref (overlay->current_composition);
1182 overlay->current_composition = NULL;
1183 overlay->current_comp_start_time = GST_CLOCK_TIME_NONE;
1184 }
1185
1186 /* Let the text task know we used that buffer */
1187 GST_CEA_CC_OVERLAY_BROADCAST (overlay);
1188 }
1189
1190 static void
gst_cea_cc_overlay_image_to_argb(guchar * pixbuf,cea708Window * window,int stride)1191 gst_cea_cc_overlay_image_to_argb (guchar * pixbuf,
1192 cea708Window * window, int stride)
1193 {
1194 int i, j;
1195 guchar *p, *bitp;
1196 int width, height;
1197
1198 width = window->image_width;
1199 height = window->image_height;
1200
1201 for (i = 0; i < height; i++) {
1202 p = pixbuf + i * stride;
1203 bitp = window->text_image + i * width * 4;
1204
1205 for (j = 0; j < width; j++) {
1206 p[0] = bitp[CAIRO_ARGB_A];
1207 p[1] = bitp[CAIRO_ARGB_R];
1208 p[2] = bitp[CAIRO_ARGB_G];
1209 p[3] = bitp[CAIRO_ARGB_B];
1210
1211 /* Cairo uses pre-multiplied ARGB, unpremultiply it */
1212 CAIRO_UNPREMULTIPLY (p[0], p[1], p[2], p[3]);
1213
1214 bitp += 4;
1215 p += 4;
1216 }
1217 }
1218 }
1219
1220 static void
gst_cea_cc_overlay_image_to_ayuv(guchar * pixbuf,cea708Window * window,int stride)1221 gst_cea_cc_overlay_image_to_ayuv (guchar * pixbuf,
1222 cea708Window * window, int stride)
1223 {
1224 int y; /* text bitmap coordinates */
1225 guchar *p, *bitp;
1226 guchar a, r, g, b;
1227 int width, height;
1228
1229 width = window->image_width;
1230 height = window->image_height;
1231
1232 for (y = 0; y < height; y++) {
1233 int n;
1234 p = pixbuf + y * stride;
1235 bitp = window->text_image + y * width * 4;
1236
1237 for (n = 0; n < width; n++) {
1238 b = bitp[CAIRO_ARGB_B];
1239 g = bitp[CAIRO_ARGB_G];
1240 r = bitp[CAIRO_ARGB_R];
1241 a = bitp[CAIRO_ARGB_A];
1242 bitp += 4;
1243
1244 /* Cairo uses pre-multiplied ARGB, unpremultiply it */
1245 CAIRO_UNPREMULTIPLY (a, r, g, b);
1246
1247 *p++ = a;
1248 *p++ = CLAMP ((int) (((19595 * r) >> 16) + ((38470 * g) >> 16) +
1249 ((7471 * b) >> 16)), 0, 255);
1250 *p++ = CLAMP ((int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) +
1251 ((32768 * b) >> 16) + 128), 0, 255);
1252 *p++ = CLAMP ((int) (((32768 * r) >> 16) - ((27439 * g) >> 16) -
1253 ((5329 * b) >> 16) + 128), 0, 255);
1254 }
1255 }
1256 }
1257
1258 static void
gst_cea_cc_overlay_create_and_push_buffer(GstCeaCcOverlay * overlay)1259 gst_cea_cc_overlay_create_and_push_buffer (GstCeaCcOverlay * overlay)
1260 {
1261 Cea708Dec *decoder = overlay->decoder;
1262 GstBuffer *outbuf;
1263 GstMapInfo map;
1264 guint8 *window_image;
1265 gint n;
1266 guint window_id;
1267 cea708Window *window;
1268 guint v_anchor = 0;
1269 guint h_anchor = 0;
1270 GstVideoOverlayComposition *comp = NULL;
1271 GstVideoOverlayRectangle *rect = NULL;
1272 GST_CEA_CC_OVERLAY_LOCK (overlay);
1273
1274 for (window_id = 0; window_id < 8; window_id++) {
1275 window = decoder->cc_windows[window_id];
1276
1277 if (!window->updated) {
1278 continue;
1279 }
1280 if (!window->deleted && window->visible && window->text_image != NULL) {
1281 GST_DEBUG_OBJECT (overlay, "Allocating buffer");
1282 outbuf =
1283 gst_buffer_new_and_alloc (window->image_width *
1284 window->image_height * 4);
1285 gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
1286 window_image = map.data;
1287 if (decoder->use_ARGB) {
1288 memset (window_image, 0,
1289 window->image_width * window->image_height * 4);
1290 gst_buffer_add_video_meta (outbuf, GST_VIDEO_FRAME_FLAG_NONE,
1291 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB, window->image_width,
1292 window->image_height);
1293 } else {
1294 for (n = 0; n < window->image_width * window->image_height; n++) {
1295 window_image[n * 4] = window_image[n * 4 + 1] = 0;
1296 window_image[n * 4 + 2] = window_image[n * 4 + 3] = 128;
1297 }
1298 gst_buffer_add_video_meta (outbuf, GST_VIDEO_FRAME_FLAG_NONE,
1299 GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV, window->image_width,
1300 window->image_height);
1301 }
1302
1303 v_anchor = window->screen_vertical * overlay->height / 100;
1304 switch (overlay->default_window_h_pos) {
1305 case GST_CEA_CC_OVERLAY_WIN_H_LEFT:
1306 window->h_offset = 0;
1307 break;
1308 case GST_CEA_CC_OVERLAY_WIN_H_CENTER:
1309 window->h_offset = (overlay->width - window->image_width) / 2;
1310 break;
1311 case GST_CEA_CC_OVERLAY_WIN_H_RIGHT:
1312 window->h_offset = overlay->width - window->image_width;
1313 break;
1314 case GST_CEA_CC_OVERLAY_WIN_H_AUTO:
1315 default:
1316 switch (window->anchor_point) {
1317 case ANCHOR_PT_TOP_LEFT:
1318 case ANCHOR_PT_MIDDLE_LEFT:
1319 case ANCHOR_PT_BOTTOM_LEFT:
1320 window->h_offset = h_anchor;
1321 break;
1322
1323 case ANCHOR_PT_TOP_CENTER:
1324 case ANCHOR_PT_CENTER:
1325 case ANCHOR_PT_BOTTOM_CENTER:
1326 window->h_offset = h_anchor - window->image_width / 2;
1327 break;
1328
1329 case ANCHOR_PT_TOP_RIGHT:
1330 case ANCHOR_PT_MIDDLE_RIGHT:
1331 case ANCHOR_PT_BOTTOM_RIGHT:
1332 window->h_offset = h_anchor - window->image_width;
1333 break;
1334 default:
1335 break;
1336 }
1337 break;
1338 }
1339
1340 switch (window->anchor_point) {
1341 case ANCHOR_PT_TOP_LEFT:
1342 case ANCHOR_PT_TOP_CENTER:
1343 case ANCHOR_PT_TOP_RIGHT:
1344 window->v_offset = v_anchor;
1345 break;
1346
1347 case ANCHOR_PT_MIDDLE_LEFT:
1348 case ANCHOR_PT_CENTER:
1349 case ANCHOR_PT_MIDDLE_RIGHT:
1350 window->v_offset = v_anchor - window->image_height / 2;
1351 break;
1352
1353 case ANCHOR_PT_BOTTOM_LEFT:
1354 case ANCHOR_PT_BOTTOM_CENTER:
1355 case ANCHOR_PT_BOTTOM_RIGHT:
1356 window->v_offset = v_anchor - window->image_height;
1357 break;
1358 default:
1359 break;
1360 }
1361 if (decoder->use_ARGB) {
1362 gst_cea_cc_overlay_image_to_argb (window_image, window,
1363 window->image_width * 4);
1364 } else {
1365 gst_cea_cc_overlay_image_to_ayuv (window_image, window,
1366 window->image_width * 4);
1367 }
1368 gst_buffer_unmap (outbuf, &map);
1369 GST_INFO_OBJECT (overlay,
1370 "window->anchor_point=%d,v_anchor=%d,h_anchor=%d,window->image_height=%d,window->image_width=%d, window->v_offset=%d, window->h_offset=%d,window->justify_mode=%d",
1371 window->anchor_point, v_anchor, h_anchor, window->image_height,
1372 window->image_width, window->v_offset, window->h_offset,
1373 window->justify_mode);
1374 rect =
1375 gst_video_overlay_rectangle_new_raw (outbuf, window->h_offset,
1376 window->v_offset, window->image_width, window->image_height, 0);
1377 if (comp == NULL) {
1378 comp = gst_video_overlay_composition_new (rect);
1379 } else {
1380 gst_video_overlay_composition_add_rectangle (comp, rect);
1381 }
1382 gst_video_overlay_rectangle_unref (rect);
1383 gst_buffer_unref (outbuf);
1384 }
1385 }
1386
1387 /* Wait for the previous buffer to go away */
1388 if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) {
1389 overlay->next_composition = comp;
1390 overlay->next_comp_start_time = decoder->current_time;
1391 GST_DEBUG_OBJECT (overlay,
1392 "wait for render next %p, current is %p BUFFER: next ts=%"
1393 GST_TIME_FORMAT ",current ts=%" GST_TIME_FORMAT,
1394 overlay->next_composition, overlay->current_composition,
1395 GST_TIME_ARGS (overlay->next_comp_start_time),
1396 GST_TIME_ARGS (overlay->current_comp_start_time));
1397
1398 GST_DEBUG_OBJECT (overlay, "has a closed caption buffer queued, waiting");
1399 GST_CEA_CC_OVERLAY_WAIT (overlay);
1400 GST_DEBUG_OBJECT (overlay, "resuming");
1401 if (overlay->cc_flushing) {
1402 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1403 return;
1404 }
1405 }
1406
1407 overlay->next_composition = NULL;
1408 overlay->next_comp_start_time = GST_CLOCK_TIME_NONE;
1409 overlay->current_composition = comp;
1410 overlay->current_comp_start_time = decoder->current_time;
1411 GST_DEBUG_OBJECT (overlay, "T: %" GST_TIME_FORMAT,
1412 GST_TIME_ARGS (overlay->current_comp_start_time));
1413 overlay->need_update = FALSE;
1414
1415 /* in case the video chain is waiting for a text buffer, wake it up */
1416 GST_CEA_CC_OVERLAY_BROADCAST (overlay);
1417 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1418 }
1419
1420 static void
gst_cea_cc_overlay_process_packet(GstCeaCcOverlay * overlay,guint8 cc_type)1421 gst_cea_cc_overlay_process_packet (GstCeaCcOverlay * overlay, guint8 cc_type)
1422 {
1423 gint16 *index = NULL;
1424 guint8 *buffer = NULL;
1425 guint8 *dtvcc_buffer = NULL;
1426 gboolean need_render = FALSE;
1427
1428 switch (cc_type) {
1429 case CCTYPE_608_CC1:
1430 case CCTYPE_608_CC2:
1431 index = &overlay->cea608_index[cc_type];
1432 buffer = overlay->cea608_buffer[cc_type];
1433 break;
1434
1435 case CCTYPE_708_ADD:
1436 case CCTYPE_708_START:
1437 index = &overlay->cea708_index;
1438 buffer = overlay->cea708_buffer;
1439 break;
1440 default:
1441 GST_ERROR_OBJECT (overlay,
1442 "attempted to process packet for unknown cc_type %d", cc_type);
1443 return;
1444 }
1445
1446 if (*index > 0) {
1447 /*TODO: in future need add 608 decoder, currently only deal with 708 */
1448 if (cc_type == CCTYPE_708_ADD || cc_type == CCTYPE_708_START) {
1449 GST_LOG_OBJECT (overlay,
1450 "called - buf[%" G_GINT16_FORMAT "] = %02X:%02X:%02X:%02X", *index,
1451 buffer[0], buffer[1], buffer[2], buffer[3]);
1452 dtvcc_buffer = g_malloc0 (*index + 1);
1453 memcpy (dtvcc_buffer, buffer, *index);
1454 need_render =
1455 gst_cea708dec_process_dtvcc_packet (overlay->decoder, dtvcc_buffer,
1456 *index);
1457 g_free (dtvcc_buffer);
1458 if (need_render)
1459 gst_cea_cc_overlay_create_and_push_buffer (overlay);
1460 }
1461 }
1462 *index = 0;
1463 }
1464
1465
1466 /**
1467 * gst_cea_cc_overlay_user_data_decode:
1468 * @overlay: The #GstCeaCcOverlay
1469 * @user_data: The #GstMpegVideoCCData to decode
1470 *
1471 * decode closed caption data and render when neccesary
1472 * in struct GstMpegVideoCCData type's user_data's data field, 3 byte's data construct 1 cc_data_pkt
1473 *
1474 * A cc_data_pkt is 3 bytes as follows:
1475 * -------------------------------------------
1476 * 5 bits (b7-b3) marker_bits (should be all 1's)
1477 * 1 bit (b2) cc_valid
1478 * 2 bits (b1-b0) cc_type (bslbf)
1479 * 8 bits cc_data_1 (bslbf)
1480 * 8 bits cc_data_2 (bslbf)
1481 *
1482 * If cc_valid != 1, then ignore this packet
1483 *
1484 * cc_type has these values:
1485 * 0 NTSC_CC_FIELD_1 - CEA-608
1486 * 1 NTSC_CC_FIELD_2 - CEA-608
1487 * 2 DTVCC_PACKET_DATA - CEA-708
1488 * 3 DTVCC_PACKET_START - CEA-708
1489 *
1490 * DTVCC packet (aka. caption channel packet)
1491 * This is formed by accumulating cc_data_1/cc_data_2 from each cc_data_pkt
1492 * starting with a packet where cc_type = 3, and ending with a packet
1493 * where again cc_type = 3 (start of next buffer), or cc_valid=0 && cc_type=2
1494 * DTVCC packet's structure is:
1495 * --------------------------------------------------------------------------
1496 * 2 bits (b6-b7) sequence_number
1497 * 6 bits (b0-b5) packet_size
1498 * ((packet_size*2-1)&0xFF) * 8 bits packet_data (Service Block)
1499 */
1500 static void
gst_cea_cc_overlay_user_data_decode(GstCeaCcOverlay * overlay,const guint8 * ccdata,gsize ccsize)1501 gst_cea_cc_overlay_user_data_decode (GstCeaCcOverlay * overlay,
1502 const guint8 * ccdata, gsize ccsize)
1503 {
1504 guint8 temp;
1505 guint8 cc_count;
1506 guint i;
1507 guint8 cc_type;
1508 guint8 cc_valid;
1509 guint8 cc_data[2];
1510
1511 cc_count = ccsize / 3;
1512
1513 for (i = 0; i < cc_count; i++) {
1514 temp = *ccdata++;
1515 cc_data[0] = *ccdata++;
1516 cc_data[1] = *ccdata++;
1517 cc_valid = (temp & CCTYPE_VALID_MASK) ? TRUE : FALSE;
1518 cc_type = (temp & CCTYPE_TYPE_MASK);
1519
1520 GST_LOG_OBJECT (overlay, "cc_data_pkt(%d): cc_valid=%d cc_type=%d "
1521 "cc_data[0]=0x%02X cc_data[1]=0x%02X",
1522 i, cc_valid, cc_type, cc_data[0], cc_data[1]);
1523
1524 /* accumulate dvtcc packet */
1525 switch (cc_type) {
1526 case CCTYPE_608_CC1:
1527 case CCTYPE_608_CC2:
1528 if (cc_valid) {
1529 if (overlay->cea608_index[cc_type] <= DTVCC_LENGTH - 2) {
1530 size_t j;
1531 for (j = 0; j < 2; ++j) {
1532 if ((cc_data[j] < ' ') || (cc_data[j] > '~')) {
1533 gst_cea_cc_overlay_process_packet (overlay, cc_type);
1534 }
1535 overlay->cea608_buffer[cc_type][overlay->
1536 cea608_index[cc_type]++] = cc_data[j];
1537 }
1538 } else {
1539 GST_ERROR_OBJECT (overlay, "cea608_buffer[%d] overflow!", cc_type);
1540 }
1541 }
1542 break;
1543
1544 case CCTYPE_708_ADD:
1545 case CCTYPE_708_START:
1546 if (cc_valid) {
1547 if (cc_type == CCTYPE_708_START) {
1548 /* The previous packet is complete */
1549 gst_cea_cc_overlay_process_packet (overlay, cc_type);
1550 }
1551 /* Add on to the current DTVCC packet */
1552 if (overlay->cea708_index <= DTVCC_LENGTH - 2) {
1553 overlay->cea708_buffer[overlay->cea708_index++] = cc_data[0];
1554 overlay->cea708_buffer[overlay->cea708_index++] = cc_data[1];
1555 } else {
1556 GST_ERROR_OBJECT (overlay, "cea708_buffer overflow!");
1557 }
1558 } else if (cc_type == CCTYPE_708_ADD) {
1559 /* This packet should be ignored, but if there is a current */
1560 /* DTVCC packet then this is the end. */
1561 gst_cea_cc_overlay_process_packet (overlay, cc_type);
1562 }
1563 break;
1564 }
1565 }
1566 }
1567
1568 /* FIXME : Move to GstVideo ANC/CC helper library */
1569 static gboolean
extract_ccdata_from_cdp(const guint8 * indata,gsize insize,const guint8 ** ccdata,gsize * ccsize)1570 extract_ccdata_from_cdp (const guint8 * indata, gsize insize,
1571 const guint8 ** ccdata, gsize * ccsize)
1572 {
1573 GstByteReader br;
1574 guint8 cdp_length;
1575 guint8 flags;
1576 #ifndef GST_DISABLE_GST_DEBUG
1577 guint8 framerate_code;
1578 guint16 seqhdr;
1579 #endif
1580
1581 GST_MEMDUMP ("CDP", indata, insize);
1582
1583 gst_byte_reader_init (&br, indata, insize);
1584
1585 /* The smallest valid CDP we are interested in is 7 (header) + 2 (cc
1586 * section) + 4 (footer) bytes long */
1587 if (gst_byte_reader_get_remaining (&br) < 13)
1588 return FALSE;
1589
1590 /* Check header */
1591 if (gst_byte_reader_get_uint16_be_unchecked (&br) != 0x9669) {
1592 GST_WARNING ("Invalid CDP header");
1593 return FALSE;
1594 }
1595 cdp_length = gst_byte_reader_get_uint8_unchecked (&br);
1596 if (cdp_length > insize) {
1597 GST_WARNING ("CDP too small (need %d bytes, have %" G_GSIZE_FORMAT ")",
1598 cdp_length, insize);
1599 return FALSE;
1600 }
1601 #ifndef GST_DISABLE_GST_DEBUG
1602 framerate_code = gst_byte_reader_get_uint8_unchecked (&br) >> 4;
1603 #else
1604 gst_byte_reader_skip (&br, 1);
1605 #endif
1606 flags = gst_byte_reader_get_uint8_unchecked (&br);
1607 #ifndef GST_DISABLE_GST_DEBUG
1608 seqhdr = gst_byte_reader_get_uint16_be_unchecked (&br);
1609 #else
1610 gst_byte_reader_skip (&br, 2);
1611 #endif
1612
1613 GST_DEBUG
1614 ("framerate_code : 0x%02x , flags : 0x%02x , sequencer_counter : %u",
1615 framerate_code, flags, seqhdr);
1616
1617 /* Skip timecode if present */
1618 if (flags & 0x80) {
1619 GST_LOG ("Skipping timecode section");
1620 gst_byte_reader_skip (&br, 5);
1621 }
1622
1623 /* cc data */
1624 if (flags & 0x40) {
1625 guint8 ccid, cc_count;
1626 if (!gst_byte_reader_get_uint8 (&br, &ccid) ||
1627 !gst_byte_reader_get_uint8 (&br, &cc_count))
1628 return FALSE;
1629 if (ccid != 0x72) {
1630 GST_WARNING ("Invalid ccdata_id (expected 0x72, got 0x%02x)", ccid);
1631 return FALSE;
1632 }
1633 cc_count &= 0x1f;
1634 if (!gst_byte_reader_get_data (&br, cc_count * 3, ccdata)) {
1635 GST_WARNING ("Not enough ccdata");
1636 *ccdata = NULL;
1637 *ccsize = 0;
1638 return FALSE;
1639 }
1640 *ccsize = cc_count * 3;
1641 }
1642
1643 /* FIXME : Parse/validate the rest of the CDP ! */
1644
1645 return TRUE;
1646 }
1647
1648 /* We receive text buffers here. If they are out of segment we just ignore them.
1649 If the buffer is in our segment we keep it internally except if another one
1650 is already waiting here, in that case we wait that it gets kicked out */
1651 static GstFlowReturn
gst_cea_cc_overlay_cc_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)1652 gst_cea_cc_overlay_cc_chain (GstPad * pad, GstObject * parent,
1653 GstBuffer * buffer)
1654 {
1655 GstFlowReturn ret = GST_FLOW_OK;
1656 GstCeaCcOverlay *overlay = (GstCeaCcOverlay *) parent;
1657 gboolean in_seg = FALSE;
1658 guint64 clip_start = 0, clip_stop = 0;
1659
1660 GST_CEA_CC_OVERLAY_LOCK (overlay);
1661
1662 if (overlay->cc_flushing) {
1663 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1664 ret = GST_FLOW_FLUSHING;
1665 GST_LOG_OBJECT (overlay, "closed caption flushing");
1666 goto beach;
1667 }
1668
1669 if (overlay->cc_eos) {
1670 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1671 ret = GST_FLOW_EOS;
1672 GST_LOG_OBJECT (overlay, "closed caption EOS");
1673 goto beach;
1674 }
1675
1676 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1677 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1678 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1679 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1680 GST_BUFFER_DURATION (buffer)));
1681
1682 if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1683 GstClockTime stop;
1684
1685 if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1686 stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1687 else
1688 stop = GST_CLOCK_TIME_NONE;
1689
1690 in_seg = gst_segment_clip (&overlay->cc_segment, GST_FORMAT_TIME,
1691 GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1692 GST_LOG_OBJECT (overlay, "stop:%" GST_TIME_FORMAT ", in_seg: %d",
1693 GST_TIME_ARGS (stop), in_seg);
1694 } else {
1695 in_seg = TRUE;
1696 }
1697
1698
1699 if (in_seg) {
1700 GstMapInfo buf_map = { 0 };
1701 const guint8 *ccdata = NULL;
1702 gsize ccsize = 0;
1703
1704 overlay->cc_segment.position = clip_start;
1705 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1706
1707 gst_buffer_map (buffer, &buf_map, GST_MAP_READ);
1708 if (overlay->is_cdp) {
1709 extract_ccdata_from_cdp (buf_map.data, buf_map.size, &ccdata, &ccsize);
1710 } else {
1711 ccdata = buf_map.data;
1712 ccsize = buf_map.size;
1713 }
1714 if (ccsize) {
1715 gst_cea_cc_overlay_user_data_decode (overlay, ccdata, ccsize);
1716 overlay->decoder->current_time = GST_BUFFER_PTS (buffer);
1717 }
1718 gst_buffer_unmap (buffer, &buf_map);
1719 } else {
1720 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1721 }
1722
1723 beach:
1724 gst_buffer_unref (buffer);
1725 return ret;
1726 }
1727
1728 static GstFlowReturn
gst_cea_cc_overlay_video_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)1729 gst_cea_cc_overlay_video_chain (GstPad * pad, GstObject * parent,
1730 GstBuffer * buffer)
1731 {
1732 GstCeaCcOverlay *overlay;
1733 GstFlowReturn ret = GST_FLOW_OK;
1734 gboolean in_seg = FALSE;
1735 guint64 start, stop, clip_start = 0, clip_stop = 0;
1736
1737 overlay = GST_CEA_CC_OVERLAY (parent);
1738
1739 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1740 goto missing_timestamp;
1741
1742 /* ignore buffers that are outside of the current segment */
1743 start = GST_BUFFER_TIMESTAMP (buffer);
1744
1745 if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
1746 stop = GST_CLOCK_TIME_NONE;
1747 } else {
1748 stop = start + GST_BUFFER_DURATION (buffer);
1749 }
1750
1751 GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
1752 GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1753 GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
1754
1755 /* segment_clip() will adjust start unconditionally to segment_start if
1756 * no stop time is provided, so handle this ourselves */
1757 if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
1758 goto out_of_segment;
1759
1760 in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
1761 &clip_start, &clip_stop);
1762
1763 if (!in_seg)
1764 goto out_of_segment;
1765
1766 /* if the buffer is only partially in the segment, fix up stamps */
1767 if (clip_start != start || (stop != -1 && clip_stop != stop)) {
1768 GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
1769 buffer = gst_buffer_make_writable (buffer);
1770 GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1771 if (stop != -1)
1772 GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1773 }
1774
1775 /* now, after we've done the clipping, fix up end time if there's no
1776 * duration (we only use those estimated values internally though, we
1777 * don't want to set bogus values on the buffer itself) */
1778 if (stop == -1) {
1779 if (overlay->info.fps_n && overlay->info.fps_d) {
1780 GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
1781 stop = start + gst_util_uint64_scale_int (GST_SECOND,
1782 overlay->info.fps_d, overlay->info.fps_n);
1783 } else {
1784 GST_LOG_OBJECT (overlay, "no duration, assuming minimal duration");
1785 stop = start + 1; /* we need to assume some interval */
1786 }
1787 }
1788
1789 gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
1790
1791 wait_for_text_buf:
1792
1793 GST_CEA_CC_OVERLAY_LOCK (overlay);
1794
1795 if (overlay->video_flushing)
1796 goto flushing;
1797
1798 if (overlay->video_eos)
1799 goto have_eos;
1800
1801 if (overlay->silent) {
1802 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1803 ret = gst_pad_push (overlay->srcpad, buffer);
1804
1805 /* Update position */
1806 overlay->segment.position = clip_start;
1807
1808 return ret;
1809 }
1810
1811 /* Closed Caption pad not linked, rendering video only */
1812 if (!overlay->cc_pad_linked) {
1813 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1814 ret = gst_pad_push (overlay->srcpad, buffer);
1815 } else {
1816 /* Closed Caption pad linked, check if we have a text buffer queued */
1817 if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) {
1818 gboolean pop_text = FALSE, valid_text_time = TRUE;
1819
1820 GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
1821 GstClockTime next_buffer_text_running_time = GST_CLOCK_TIME_NONE;
1822 #ifndef GST_DISABLE_GST_DEBUG
1823 GstClockTime vid_running_time;
1824 #endif
1825 GstClockTime vid_running_time_end;
1826
1827 #ifndef GST_DISABLE_GST_DEBUG
1828 vid_running_time =
1829 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
1830 start);
1831 #endif
1832 vid_running_time_end =
1833 gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
1834 stop);
1835 if (GST_CLOCK_TIME_IS_VALID (overlay->next_comp_start_time)) {
1836 next_buffer_text_running_time =
1837 gst_segment_to_running_time (&overlay->cc_segment, GST_FORMAT_TIME,
1838 overlay->next_comp_start_time);
1839
1840 if (next_buffer_text_running_time < vid_running_time_end) {
1841 /* text buffer should be force updated, popping */
1842 GST_DEBUG_OBJECT (overlay,
1843 "T: next_buffer_text_running_time: %" GST_TIME_FORMAT
1844 " - overlay->next_comp_start_time: %" GST_TIME_FORMAT,
1845 GST_TIME_ARGS (next_buffer_text_running_time),
1846 GST_TIME_ARGS (overlay->next_comp_start_time));
1847 GST_DEBUG_OBJECT (overlay,
1848 "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
1849 GST_TIME_ARGS (vid_running_time),
1850 GST_TIME_ARGS (vid_running_time_end));
1851 GST_LOG_OBJECT (overlay,
1852 "text buffer should be force updated, popping");
1853 pop_text = FALSE;
1854 gst_cea_cc_overlay_pop_text (overlay);
1855 GST_CEA_CC_OVERLAY_WAIT (overlay);
1856 GST_DEBUG_OBJECT (overlay, "resuming");
1857 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1858 goto wait_for_text_buf;
1859 }
1860
1861 }
1862
1863 /* if the text buffer isn't stamped right, pop it off the
1864 * queue and display it for the current video frame only */
1865 if (!GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) {
1866 GST_WARNING_OBJECT (overlay, "Got text buffer with invalid timestamp");
1867 pop_text = TRUE;
1868 valid_text_time = FALSE;
1869 }
1870
1871 /* If timestamp and duration are valid */
1872 if (valid_text_time) {
1873 text_running_time =
1874 gst_segment_to_running_time (&overlay->cc_segment,
1875 GST_FORMAT_TIME, overlay->current_comp_start_time);
1876 }
1877
1878 GST_DEBUG_OBJECT (overlay, "T: %" GST_TIME_FORMAT,
1879 GST_TIME_ARGS (text_running_time));
1880 GST_DEBUG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
1881 GST_TIME_ARGS (vid_running_time),
1882 GST_TIME_ARGS (vid_running_time_end));
1883
1884 if (valid_text_time && vid_running_time_end <= text_running_time) {
1885 GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
1886 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1887 /* Push the video frame */
1888 ret = gst_pad_push (overlay->srcpad, buffer);
1889 } else {
1890 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1891 ret = gst_cea_cc_overlay_push_frame (overlay, buffer);
1892 }
1893 if (pop_text) {
1894 GST_CEA_CC_OVERLAY_LOCK (overlay);
1895 gst_cea_cc_overlay_pop_text (overlay);
1896 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1897 }
1898 } else {
1899 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1900 GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
1901 ret = gst_pad_push (overlay->srcpad, buffer);
1902 }
1903 }
1904
1905 /* Update position */
1906 overlay->segment.position = clip_start;
1907 GST_DEBUG_OBJECT (overlay, "ret=%d", ret);
1908
1909 return ret;
1910
1911 missing_timestamp:
1912 {
1913 GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
1914 gst_buffer_unref (buffer);
1915 return GST_FLOW_OK;
1916 }
1917
1918 flushing:
1919 {
1920 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1921 GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
1922 gst_buffer_unref (buffer);
1923 return GST_FLOW_FLUSHING;
1924 }
1925 have_eos:
1926 {
1927 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1928 GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
1929 gst_buffer_unref (buffer);
1930 return GST_FLOW_EOS;
1931 }
1932 out_of_segment:
1933 {
1934 GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
1935 gst_buffer_unref (buffer);
1936 return GST_FLOW_OK;
1937 }
1938 }
1939
1940 static GstStateChangeReturn
gst_cea_cc_overlay_change_state(GstElement * element,GstStateChange transition)1941 gst_cea_cc_overlay_change_state (GstElement * element,
1942 GstStateChange transition)
1943 {
1944 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1945 GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (element);
1946
1947 switch (transition) {
1948 case GST_STATE_CHANGE_PAUSED_TO_READY:
1949 GST_CEA_CC_OVERLAY_LOCK (overlay);
1950 overlay->cc_flushing = TRUE;
1951 overlay->video_flushing = TRUE;
1952 /* pop_text will broadcast on the GCond and thus also make the video
1953 * chain exit if it's waiting for a text buffer */
1954 gst_cea_cc_overlay_pop_text (overlay);
1955 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1956 break;
1957 default:
1958 break;
1959 }
1960
1961 ret = parent_class->change_state (element, transition);
1962 if (ret == GST_STATE_CHANGE_FAILURE)
1963 return ret;
1964
1965 switch (transition) {
1966 case GST_STATE_CHANGE_READY_TO_PAUSED:
1967 GST_CEA_CC_OVERLAY_LOCK (overlay);
1968 overlay->cc_flushing = FALSE;
1969 overlay->video_flushing = FALSE;
1970 overlay->video_eos = FALSE;
1971 overlay->cc_eos = FALSE;
1972 gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1973 gst_segment_init (&overlay->cc_segment, GST_FORMAT_TIME);
1974 GST_CEA_CC_OVERLAY_UNLOCK (overlay);
1975 break;
1976 default:
1977 break;
1978 }
1979
1980 return ret;
1981 }
1982