1 /*
2 * GStreamer
3 * Copyright (C) 2009 Sebastian Pölsterl <sebp@k-d-w.org>
4 * Copyright (C) 2010 Andoni Morales Alastruey <ylatuya@gmail.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
20 */
21
22 /**
23 * SECTION:element-teletextdec
24 * @title: teletextdec
25 *
26 * Decode a stream of raw VBI packets containing teletext information to a RGBA
27 * stream.
28 *
29 * ## Example launch line
30 * |[
31 * gst-launch-1.0 -v -m filesrc location=recording.mpeg ! tsdemux ! teletextdec ! videoconvert ! ximagesink
32 * ]|
33 *
34 */
35
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #include <gst/gst.h>
41 #include <gst/video/video.h>
42 #include <string.h>
43 #include <stdlib.h>
44
45 #include "gstteletextdec.h"
46
47 GST_DEBUG_CATEGORY_STATIC (gst_teletextdec_debug);
48 #define GST_CAT_DEFAULT gst_teletextdec_debug
49
50 #define parent_class gst_teletextdec_parent_class
51
52 #define SUBTITLES_PAGE 888
53 #define MAX_SLICES 32
54 #define DEFAULT_FONT_DESCRIPTION "verdana 12"
55 #define PANGO_TEMPLATE "<span font_desc=\"%s\" foreground=\"%s\"> %s \n</span>"
56
57 /* Filter signals and args */
58 enum
59 {
60 LAST_SIGNAL
61 };
62
63 enum
64 {
65 PROP_0,
66 PROP_PAGENO,
67 PROP_SUBNO,
68 PROP_SUBTITLES_MODE,
69 PROP_SUBS_TEMPLATE,
70 PROP_FONT_DESCRIPTION
71 };
72
73 enum
74 {
75 VBI_ERROR = -1,
76 VBI_SUCCESS = 0,
77 VBI_NEW_FRAME = 1
78 };
79
80 typedef enum
81 {
82 DATA_UNIT_EBU_TELETEXT_NON_SUBTITLE = 0x02,
83 DATA_UNIT_EBU_TELETEXT_SUBTITLE = 0x03,
84 DATA_UNIT_EBU_TELETEXT_INVERTED = 0x0C,
85
86 DATA_UNIT_ZVBI_WSS_CPR1204 = 0xB4,
87 DATA_UNIT_ZVBI_CLOSED_CAPTION_525 = 0xB5,
88 DATA_UNIT_ZVBI_MONOCHROME_SAMPLES_525 = 0xB6,
89
90 DATA_UNIT_VPS = 0xC3,
91 DATA_UNIT_WSS = 0xC4,
92 DATA_UNIT_CLOSED_CAPTION = 0xC5,
93 DATA_UNIT_MONOCHROME_SAMPLES = 0xC6,
94
95 DATA_UNIT_STUFFING = 0xFF,
96 } data_unit_id;
97
98 typedef struct
99 {
100 int pgno;
101 int subno;
102 } page_info;
103
104 typedef enum
105 {
106 SYSTEM_525 = 0,
107 SYSTEM_625
108 } systems;
109
110 /*
111 * ETS 300 706 Table 30: Colour Map
112 */
113 static const gchar *default_color_map[40] = {
114 "#000000", "#FF0000", "#00FF00", "#FFFF00", "#0000FF",
115 "#FF00FF", "#00FFFF", "#FFFFFF", "#000000", "#770000",
116 "#007700", "#777700", "#000077", "#770077", "#007777",
117 "#777777", "#FF0055", "#FF7700", "#00FF77", "#FFFFBB",
118 "#00CCAA", "#550000", "#665522", "#CC7777", "#333333",
119 "#FF7777", "#77FF77", "#FFFF77", "#7777FF", "#FF77FF",
120 "#77FFFF", "#DDD0DD",
121
122 /* Private colors */
123 "#000000", "#FFAA99", "#44EE00", "#FFDD00", "#FFAA99",
124 "#FF00FF", "#00FFFF", "#EEEEEE"
125 };
126
127 /* in RGBA mode, one character occupies 12 x 10 pixels. */
128 #define COLUMNS_TO_WIDTH(cols) ((cols) * 12)
129 #define ROWS_TO_HEIGHT(rows) ((rows) * 10)
130
131 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
132 GST_PAD_SINK,
133 GST_PAD_ALWAYS,
134 GST_STATIC_CAPS ("application/x-teletext;")
135 );
136
137 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
138 GST_PAD_SRC,
139 GST_PAD_ALWAYS,
140 GST_STATIC_CAPS
141 (GST_VIDEO_CAPS_MAKE ("RGBA") ";"
142 "text/x-raw, format={utf-8,pango-markup} ;")
143 );
144
145 G_DEFINE_TYPE (GstTeletextDec, gst_teletextdec, GST_TYPE_ELEMENT);
146
147 static void gst_teletextdec_set_property (GObject * object, guint prop_id,
148 const GValue * value, GParamSpec * pspec);
149 static void gst_teletextdec_get_property (GObject * object, guint prop_id,
150 GValue * value, GParamSpec * pspec);
151 static void gst_teletextdec_finalize (GObject * object);
152
153 static GstStateChangeReturn gst_teletextdec_change_state (GstElement *
154 element, GstStateChange transition);
155
156 static GstFlowReturn gst_teletextdec_chain (GstPad * pad, GstObject * parent,
157 GstBuffer * buffer);
158 static gboolean gst_teletextdec_sink_event (GstPad * pad, GstObject * parent,
159 GstEvent * event);
160 static gboolean gst_teletextdec_src_event (GstPad * pad, GstObject * parent,
161 GstEvent * event);
162
163 static void gst_teletextdec_event_handler (vbi_event * ev, void *user_data);
164
165 static GstFlowReturn gst_teletextdec_push_page (GstTeletextDec * teletext);
166 static GstFlowReturn gst_teletextdec_export_text_page (GstTeletextDec *
167 teletext, vbi_page * page, GstBuffer ** buf);
168 static GstFlowReturn gst_teletextdec_export_rgba_page (GstTeletextDec *
169 teletext, vbi_page * page, GstBuffer ** buf);
170 static GstFlowReturn gst_teletextdec_export_pango_page (GstTeletextDec *
171 teletext, vbi_page * page, GstBuffer ** buf);
172
173 static void gst_teletextdec_process_telx_buffer (GstTeletextDec * teletext,
174 GstBuffer * buf);
175 static gboolean gst_teletextdec_extract_data_units (GstTeletextDec *
176 teletext, GstTeletextFrame * f, const guint8 * packet, guint * offset,
177 gsize size);
178
179 static void gst_teletextdec_zvbi_init (GstTeletextDec * teletext);
180 static void gst_teletextdec_zvbi_clear (GstTeletextDec * teletext);
181 static void gst_teletextdec_reset_frame (GstTeletextDec * teletext);
182
183 /* initialize the gstteletext's class */
184 static void
gst_teletextdec_class_init(GstTeletextDecClass * klass)185 gst_teletextdec_class_init (GstTeletextDecClass * klass)
186 {
187 GObjectClass *gobject_class;
188 GstElementClass *gstelement_class;
189
190 gobject_class = G_OBJECT_CLASS (klass);
191 gobject_class->set_property = gst_teletextdec_set_property;
192 gobject_class->get_property = gst_teletextdec_get_property;
193 gobject_class->finalize = gst_teletextdec_finalize;
194
195 gstelement_class = GST_ELEMENT_CLASS (klass);
196 gstelement_class->change_state =
197 GST_DEBUG_FUNCPTR (gst_teletextdec_change_state);
198
199 g_object_class_install_property (gobject_class, PROP_PAGENO,
200 g_param_spec_int ("page", "Page number",
201 "Number of page that should displayed",
202 100, 999, 100, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
203
204 g_object_class_install_property (gobject_class, PROP_SUBNO,
205 g_param_spec_int ("subpage", "Sub-page number",
206 "Number of sub-page that should displayed (-1 for all)",
207 -1, 0x99, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
208
209 g_object_class_install_property (gobject_class, PROP_SUBTITLES_MODE,
210 g_param_spec_boolean ("subtitles-mode", "Enable subtitles mode",
211 "Enables subtitles mode for text output stripping the blank lines and "
212 "the teletext state lines", FALSE,
213 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
214
215 g_object_class_install_property (gobject_class, PROP_SUBS_TEMPLATE,
216 g_param_spec_string ("subtitles-template", "Subtitles output template",
217 "Output template used to print each one of the subtitles lines",
218 g_strescape ("%s\n", NULL),
219 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
220
221 g_object_class_install_property (gobject_class, PROP_FONT_DESCRIPTION,
222 g_param_spec_string ("font-description", "Pango font description",
223 "Font description used for the pango output.",
224 DEFAULT_FONT_DESCRIPTION,
225 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
226
227 gst_element_class_set_static_metadata (gstelement_class,
228 "Teletext decoder",
229 "Decoder",
230 "Decode a raw VBI stream containing teletext information to RGBA and text",
231 "Sebastian Pölsterl <sebp@k-d-w.org>, "
232 "Andoni Morales Alastruey <ylatuya@gmail.com>");
233
234 gst_element_class_add_static_pad_template (gstelement_class, &src_template);
235 gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
236 }
237
238 /* initialize the new element
239 * initialize instance structure
240 */
241 static void
gst_teletextdec_init(GstTeletextDec * teletext)242 gst_teletextdec_init (GstTeletextDec * teletext)
243 {
244 /* Create sink pad */
245 teletext->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
246 gst_pad_set_chain_function (teletext->sinkpad,
247 GST_DEBUG_FUNCPTR (gst_teletextdec_chain));
248 gst_pad_set_event_function (teletext->sinkpad,
249 GST_DEBUG_FUNCPTR (gst_teletextdec_sink_event));
250 gst_element_add_pad (GST_ELEMENT (teletext), teletext->sinkpad);
251
252 /* Create src pad */
253 teletext->srcpad = gst_pad_new_from_static_template (&src_template, "src");
254 gst_pad_set_event_function (teletext->srcpad,
255 GST_DEBUG_FUNCPTR (gst_teletextdec_src_event));
256 gst_element_add_pad (GST_ELEMENT (teletext), teletext->srcpad);
257
258 teletext->segment = NULL;
259 teletext->decoder = NULL;
260 teletext->pageno = 0x100;
261 teletext->subno = -1;
262 teletext->subtitles_mode = FALSE;
263 teletext->subtitles_template = g_strescape ("%s\n", NULL);
264 teletext->font_description = g_strdup (DEFAULT_FONT_DESCRIPTION);
265
266 teletext->in_timestamp = GST_CLOCK_TIME_NONE;
267 teletext->in_duration = GST_CLOCK_TIME_NONE;
268
269 teletext->rate_numerator = 0;
270 teletext->rate_denominator = 1;
271
272 teletext->queue = NULL;
273 g_mutex_init (&teletext->queue_lock);
274
275 gst_teletextdec_reset_frame (teletext);
276
277 teletext->last_ts = 0;
278
279 teletext->export_func = NULL;
280 teletext->buf_pool = NULL;
281 }
282
283 static void
gst_teletextdec_finalize(GObject * object)284 gst_teletextdec_finalize (GObject * object)
285 {
286 GstTeletextDec *teletext = GST_TELETEXTDEC (object);
287
288 g_mutex_clear (&teletext->queue_lock);
289
290 g_free (teletext->font_description);
291 g_free (teletext->subtitles_template);
292 g_free (teletext->frame);
293
294 G_OBJECT_CLASS (parent_class)->finalize (object);
295 }
296
297 static void
gst_teletextdec_zvbi_init(GstTeletextDec * teletext)298 gst_teletextdec_zvbi_init (GstTeletextDec * teletext)
299 {
300 g_return_if_fail (teletext != NULL);
301
302 GST_LOG_OBJECT (teletext, "Initializing structures");
303
304 teletext->decoder = vbi_decoder_new ();
305
306 vbi_event_handler_register (teletext->decoder,
307 VBI_EVENT_TTX_PAGE | VBI_EVENT_CAPTION,
308 gst_teletextdec_event_handler, teletext);
309
310 g_mutex_lock (&teletext->queue_lock);
311 teletext->queue = g_queue_new ();
312 g_mutex_unlock (&teletext->queue_lock);
313 }
314
315 static void
gst_teletextdec_zvbi_clear(GstTeletextDec * teletext)316 gst_teletextdec_zvbi_clear (GstTeletextDec * teletext)
317 {
318 g_return_if_fail (teletext != NULL);
319
320 GST_LOG_OBJECT (teletext, "Clearing structures");
321
322 if (teletext->decoder != NULL) {
323 vbi_decoder_delete (teletext->decoder);
324 teletext->decoder = NULL;
325 }
326 if (teletext->frame != NULL) {
327 if (teletext->frame->sliced_begin)
328 g_free (teletext->frame->sliced_begin);
329 g_free (teletext->frame);
330 teletext->frame = NULL;
331 }
332
333 g_mutex_lock (&teletext->queue_lock);
334 if (teletext->queue != NULL) {
335 g_queue_free (teletext->queue);
336 teletext->queue = NULL;
337 }
338 g_mutex_unlock (&teletext->queue_lock);
339
340 teletext->in_timestamp = GST_CLOCK_TIME_NONE;
341 teletext->in_duration = GST_CLOCK_TIME_NONE;
342 teletext->pageno = 0x100;
343 teletext->subno = -1;
344 teletext->last_ts = 0;
345 }
346
347 static void
gst_teletextdec_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)348 gst_teletextdec_set_property (GObject * object, guint prop_id,
349 const GValue * value, GParamSpec * pspec)
350 {
351 GstTeletextDec *teletext = GST_TELETEXTDEC (object);
352
353 switch (prop_id) {
354 case PROP_PAGENO:
355 teletext->pageno = (gint) vbi_bin2bcd (g_value_get_int (value));
356 break;
357 case PROP_SUBNO:
358 teletext->subno = g_value_get_int (value);
359 break;
360 case PROP_SUBTITLES_MODE:
361 teletext->subtitles_mode = g_value_get_boolean (value);
362 break;
363 case PROP_SUBS_TEMPLATE:
364 g_free (teletext->subtitles_template);
365 teletext->subtitles_template = g_value_dup_string (value);
366 break;
367 case PROP_FONT_DESCRIPTION:
368 g_free (teletext->font_description);
369 teletext->font_description = g_value_dup_string (value);
370 break;
371 default:
372 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
373 break;
374 }
375 }
376
377 static void
gst_teletextdec_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)378 gst_teletextdec_get_property (GObject * object, guint prop_id,
379 GValue * value, GParamSpec * pspec)
380 {
381 GstTeletextDec *teletext = GST_TELETEXTDEC (object);
382
383 switch (prop_id) {
384 case PROP_PAGENO:
385 g_value_set_int (value, (gint) vbi_bcd2dec (teletext->pageno));
386 break;
387 case PROP_SUBNO:
388 g_value_set_int (value, teletext->subno);
389 break;
390 case PROP_SUBTITLES_MODE:
391 g_value_set_boolean (value, teletext->subtitles_mode);
392 break;
393 case PROP_SUBS_TEMPLATE:
394 g_value_set_string (value, teletext->subtitles_template);
395 break;
396 case PROP_FONT_DESCRIPTION:
397 g_value_set_string (value, teletext->font_description);
398 break;
399 default:
400 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
401 break;
402 }
403 }
404
405 static gboolean
gst_teletextdec_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)406 gst_teletextdec_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
407 {
408 gboolean ret;
409 GstTeletextDec *teletext = GST_TELETEXTDEC (parent);
410
411 GST_DEBUG_OBJECT (teletext, "got event %s",
412 gst_event_type_get_name (GST_EVENT_TYPE (event)));
413
414 switch (GST_EVENT_TYPE (event)) {
415 case GST_EVENT_SEGMENT:
416 /* maybe save and/or update the current segment (e.g. for output
417 * clipping) or convert the event into one in a different format
418 * (e.g. BYTES to TIME) or drop it and set a flag to send a newsegment
419 * event in a different format later */
420 if (NULL == teletext->export_func) {
421 /* save the segment event and send it after sending caps. replace the
422 * old event if present. */
423 if (teletext->segment) {
424 gst_event_unref (teletext->segment);
425 }
426 teletext->segment = event;
427 ret = TRUE;
428 } else {
429 ret = gst_pad_push_event (teletext->srcpad, event);
430 }
431 break;
432 case GST_EVENT_EOS:
433 /* end-of-stream, we should close down all stream leftovers here */
434 gst_teletextdec_zvbi_clear (teletext);
435 ret = gst_pad_push_event (teletext->srcpad, event);
436 break;
437 case GST_EVENT_FLUSH_STOP:
438 gst_teletextdec_zvbi_clear (teletext);
439 gst_teletextdec_zvbi_init (teletext);
440 ret = gst_pad_push_event (teletext->srcpad, event);
441 break;
442 default:
443 ret = gst_pad_event_default (pad, parent, event);
444 break;
445 }
446
447 return ret;
448 }
449
450 static gboolean
gst_teletextdec_src_event(GstPad * pad,GstObject * parent,GstEvent * event)451 gst_teletextdec_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
452 {
453 gboolean ret;
454 GstTeletextDec *teletext = GST_TELETEXTDEC (parent);
455
456 switch (GST_EVENT_TYPE (event)) {
457 case GST_EVENT_RECONFIGURE:
458 /* setting export_func to NULL will cause the element to renegotiate caps
459 * before pushing a buffer. */
460 teletext->export_func = NULL;
461 ret = TRUE;
462 break;
463
464 default:
465 ret = gst_pad_event_default (pad, parent, event);
466 break;
467 }
468 return ret;
469 }
470
471 static GstStateChangeReturn
gst_teletextdec_change_state(GstElement * element,GstStateChange transition)472 gst_teletextdec_change_state (GstElement * element, GstStateChange transition)
473 {
474 GstStateChangeReturn ret;
475 GstTeletextDec *teletext;
476
477 teletext = GST_TELETEXTDEC (element);
478
479 switch (transition) {
480 case GST_STATE_CHANGE_READY_TO_PAUSED:
481 gst_teletextdec_zvbi_init (teletext);
482 break;
483 default:
484 break;
485 }
486
487 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
488 if (ret != GST_STATE_CHANGE_SUCCESS)
489 return ret;
490
491 switch (transition) {
492 case GST_STATE_CHANGE_PAUSED_TO_READY:
493 gst_teletextdec_zvbi_clear (teletext);
494 break;
495 default:
496 break;
497 }
498
499 return ret;
500 }
501
502 static void
gst_teletextdec_reset_frame(GstTeletextDec * teletext)503 gst_teletextdec_reset_frame (GstTeletextDec * teletext)
504 {
505 if (teletext->frame == NULL)
506 teletext->frame = g_new0 (GstTeletextFrame, 1);
507 if (teletext->frame->sliced_begin == NULL)
508 teletext->frame->sliced_begin = g_new (vbi_sliced, MAX_SLICES);
509 teletext->frame->current_slice = teletext->frame->sliced_begin;
510 teletext->frame->sliced_end = teletext->frame->sliced_begin + MAX_SLICES;
511 teletext->frame->last_field = 0;
512 teletext->frame->last_field_line = 0;
513 teletext->frame->last_frame_line = 0;
514 }
515
516 static void
gst_teletextdec_process_telx_buffer(GstTeletextDec * teletext,GstBuffer * buf)517 gst_teletextdec_process_telx_buffer (GstTeletextDec * teletext, GstBuffer * buf)
518 {
519 GstMapInfo buf_map;
520 guint offset = 0;
521 gint res;
522 gst_buffer_map (buf, &buf_map, GST_MAP_READ);
523
524 teletext->in_timestamp = GST_BUFFER_TIMESTAMP (buf);
525 teletext->in_duration = GST_BUFFER_DURATION (buf);
526
527 if (teletext->frame == NULL)
528 gst_teletextdec_reset_frame (teletext);
529
530 while (offset < buf_map.size) {
531 res =
532 gst_teletextdec_extract_data_units (teletext, teletext->frame,
533 buf_map.data, &offset, buf_map.size);
534
535 if (res == VBI_NEW_FRAME) {
536 /* We have a new frame, it's time to feed the decoder */
537 vbi_sliced *s;
538 gint n_lines;
539
540 n_lines = teletext->frame->current_slice - teletext->frame->sliced_begin;
541 GST_LOG_OBJECT (teletext, "Completed frame, decoding new %d lines",
542 n_lines);
543 s = g_memdup (teletext->frame->sliced_begin,
544 n_lines * sizeof (vbi_sliced));
545 vbi_decode (teletext->decoder, s, n_lines, teletext->last_ts);
546 /* From vbi_decode():
547 * timestamp shall advance by 1/30 to 1/25 seconds whenever calling this
548 * function. Failure to do so will be interpreted as frame dropping, which
549 * starts a resynchronization cycle, eventually a channel switch may be assumed
550 * which resets even more decoder state. So even if a frame did not contain
551 * any useful data this function must be called, with lines set to zero.
552 */
553 teletext->last_ts += 0.04;
554
555 g_free (s);
556 gst_teletextdec_reset_frame (teletext);
557 } else if (res == VBI_ERROR) {
558 gst_teletextdec_reset_frame (teletext);
559 goto beach;
560 }
561 }
562 beach:
563 gst_buffer_unmap (buf, &buf_map);
564 return;
565 }
566
567 static void
gst_teletextdec_event_handler(vbi_event * ev,void * user_data)568 gst_teletextdec_event_handler (vbi_event * ev, void *user_data)
569 {
570 page_info *pi;
571 vbi_pgno pgno;
572 vbi_subno subno;
573
574 GstTeletextDec *teletext = GST_TELETEXTDEC (user_data);
575
576 switch (ev->type) {
577 case VBI_EVENT_TTX_PAGE:
578 pgno = ev->ev.ttx_page.pgno;
579 subno = ev->ev.ttx_page.subno;
580
581 if (pgno != teletext->pageno
582 || (teletext->subno != -1 && subno != teletext->subno))
583 return;
584
585 GST_DEBUG_OBJECT (teletext, "Received teletext page %03d.%02d",
586 (gint) vbi_bcd2dec (pgno), (gint) vbi_bcd2dec (subno));
587
588 pi = g_new (page_info, 1);
589 pi->pgno = pgno;
590 pi->subno = subno;
591
592 g_mutex_lock (&teletext->queue_lock);
593 g_queue_push_tail (teletext->queue, pi);
594 g_mutex_unlock (&teletext->queue_lock);
595 break;
596 case VBI_EVENT_CAPTION:
597 /* TODO: Handle subtitles in caption teletext pages */
598 GST_DEBUG_OBJECT (teletext, "Received caption page. Not implemented");
599 break;
600 default:
601 break;
602 }
603 return;
604 }
605
606 static void
gst_teletextdec_try_get_buffer_pool(GstTeletextDec * teletext,GstCaps * caps,gssize size)607 gst_teletextdec_try_get_buffer_pool (GstTeletextDec * teletext, GstCaps * caps,
608 gssize size)
609 {
610 guint pool_bufsize, min_bufs, max_bufs;
611 GstStructure *poolcfg;
612 GstBufferPool *new_pool;
613 GstQuery *alloc = gst_query_new_allocation (caps, TRUE);
614
615 if (teletext->buf_pool) {
616 /* this function is called only on a caps/size change, so it's practically
617 * impossible that we'll be able to reuse the old pool. */
618 gst_buffer_pool_set_active (teletext->buf_pool, FALSE);
619 gst_object_unref (teletext->buf_pool);
620 }
621
622 if (!gst_pad_peer_query (teletext->srcpad, alloc)) {
623 GST_DEBUG_OBJECT (teletext, "Failed to query peer pad for allocation "
624 "parameters");
625 teletext->buf_pool = NULL;
626 goto beach;
627 }
628
629 if (gst_query_get_n_allocation_pools (alloc) > 0) {
630 gst_query_parse_nth_allocation_pool (alloc, 0, &new_pool, &pool_bufsize,
631 &min_bufs, &max_bufs);
632 } else {
633 new_pool = gst_buffer_pool_new ();
634 max_bufs = 0;
635 min_bufs = 1;
636 }
637
638 poolcfg = gst_buffer_pool_get_config (new_pool);
639 gst_buffer_pool_config_set_params (poolcfg, gst_caps_copy (caps), size,
640 min_bufs, max_bufs);
641 if (!gst_buffer_pool_set_config (new_pool, poolcfg)) {
642 GST_DEBUG_OBJECT (teletext, "Failed to configure the buffer pool");
643 gst_object_unref (new_pool);
644 teletext->buf_pool = NULL;
645 goto beach;
646 }
647 if (!gst_buffer_pool_set_active (new_pool, TRUE)) {
648 GST_DEBUG_OBJECT (teletext, "Failed to make the buffer pool active");
649 gst_object_unref (new_pool);
650 teletext->buf_pool = NULL;
651 goto beach;
652 }
653
654 teletext->buf_pool = new_pool;
655
656 beach:
657 gst_query_unref (alloc);
658 }
659
660 static gboolean
gst_teletextdec_negotiate_caps(GstTeletextDec * teletext,guint width,guint height)661 gst_teletextdec_negotiate_caps (GstTeletextDec * teletext, guint width,
662 guint height)
663 {
664 gboolean rv = FALSE;
665 /* get the peer's caps filtered by our own ones. */
666 GstCaps *ourcaps = gst_pad_query_caps (teletext->srcpad, NULL);
667 GstCaps *peercaps = gst_pad_peer_query_caps (teletext->srcpad, ourcaps);
668 GstStructure *caps_struct;
669 const gchar *caps_name, *caps_fmt;
670
671 gst_caps_unref (ourcaps);
672
673 if (gst_caps_is_empty (peercaps)) {
674 goto beach;
675 }
676
677 /* make them writable in case we need to fixate them (video/x-raw). */
678 peercaps = gst_caps_make_writable (peercaps);
679 caps_struct = gst_caps_get_structure (peercaps, 0);
680 caps_name = gst_structure_get_name (caps_struct);
681 caps_fmt = gst_structure_get_string (caps_struct, "format");
682
683 if (!g_strcmp0 (caps_name, "video/x-raw")) {
684 teletext->width = width;
685 teletext->height = height;
686 teletext->export_func = gst_teletextdec_export_rgba_page;
687 gst_structure_set (caps_struct,
688 "width", G_TYPE_INT, width,
689 "height", G_TYPE_INT, height,
690 "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
691 } else if (!g_strcmp0 (caps_name, "text/x-raw") &&
692 !g_strcmp0 (caps_fmt, "utf-8")) {
693 teletext->export_func = gst_teletextdec_export_text_page;
694 } else if (!g_strcmp0 (caps_name, "text/x-raw") &&
695 !g_strcmp0 (caps_fmt, "pango-markup")) {
696 teletext->export_func = gst_teletextdec_export_pango_page;
697 } else {
698 goto beach;
699 }
700
701 if (!gst_pad_push_event (teletext->srcpad, gst_event_new_caps (peercaps))) {
702 goto beach;
703 }
704
705 /* try to get a bufferpool from the peer pad in case of RGBA output. */
706 if (gst_teletextdec_export_rgba_page == teletext->export_func) {
707 gst_teletextdec_try_get_buffer_pool (teletext, peercaps,
708 width * height * sizeof (vbi_rgba));
709 }
710
711 /* we can happily return a success now. */
712 rv = TRUE;
713
714 beach:
715 gst_caps_unref (peercaps);
716 return rv;
717 }
718
719 /* this function does the actual processing
720 */
721 static GstFlowReturn
gst_teletextdec_chain(GstPad * pad,GstObject * parent,GstBuffer * buf)722 gst_teletextdec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
723 {
724 GstTeletextDec *teletext = GST_TELETEXTDEC (parent);
725 GstFlowReturn ret = GST_FLOW_OK;
726
727 teletext->in_timestamp = GST_BUFFER_TIMESTAMP (buf);
728 teletext->in_duration = GST_BUFFER_DURATION (buf);
729
730 gst_teletextdec_process_telx_buffer (teletext, buf);
731 gst_buffer_unref (buf);
732
733 g_mutex_lock (&teletext->queue_lock);
734 if (!g_queue_is_empty (teletext->queue)) {
735 ret = gst_teletextdec_push_page (teletext);
736 if (ret != GST_FLOW_OK) {
737 g_mutex_unlock (&teletext->queue_lock);
738 goto error;
739 }
740 }
741 g_mutex_unlock (&teletext->queue_lock);
742
743 return ret;
744
745 /* ERRORS */
746 error:
747 {
748 if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED
749 && ret != GST_FLOW_FLUSHING) {
750 GST_ELEMENT_FLOW_ERROR (teletext, ret);
751 return GST_FLOW_ERROR;
752 }
753 return ret;
754 }
755 }
756
757 static GstFlowReturn
gst_teletextdec_push_page(GstTeletextDec * teletext)758 gst_teletextdec_push_page (GstTeletextDec * teletext)
759 {
760 GstFlowReturn ret = GST_FLOW_OK;
761 GstBuffer *buf;
762 vbi_page page;
763 page_info *pi;
764 gint pgno, subno;
765 gboolean success;
766 guint width, height;
767
768 pi = g_queue_pop_head (teletext->queue);
769 pgno = vbi_bcd2dec (pi->pgno);
770 subno = vbi_bcd2dec (pi->subno);
771
772 GST_INFO_OBJECT (teletext, "Fetching teletext page %03d.%02d", pgno, subno);
773
774 success = vbi_fetch_vt_page (teletext->decoder, &page, pi->pgno, pi->subno,
775 VBI_WST_LEVEL_3p5, 25, FALSE);
776 g_free (pi);
777 if (G_UNLIKELY (!success))
778 goto fetch_page_failed;
779
780 width = COLUMNS_TO_WIDTH (page.columns);
781 height = ROWS_TO_HEIGHT (page.rows);
782
783 /* if output_func is NULL, we need to (re-)negotiate. also, it is possible
784 * (though unlikely) that we received a page of a different size. */
785 if (G_UNLIKELY (NULL == teletext->export_func ||
786 teletext->width != width || teletext->height != height)) {
787 /* if negotiate_caps returns FALSE, that means we weren't able to
788 * negotiate. */
789 if (G_UNLIKELY (!gst_teletextdec_negotiate_caps (teletext, width, height))) {
790 ret = GST_FLOW_NOT_NEGOTIATED;
791 goto push_failed;
792 }
793 if (G_UNLIKELY (teletext->segment)) {
794 gst_pad_push_event (teletext->srcpad, teletext->segment);
795 teletext->segment = NULL;
796 }
797 }
798
799 teletext->export_func (teletext, &page, &buf);
800 vbi_unref_page (&page);
801
802 GST_BUFFER_TIMESTAMP (buf) = teletext->in_timestamp;
803 GST_BUFFER_DURATION (buf) = teletext->in_duration;
804
805 GST_INFO_OBJECT (teletext, "Pushing buffer of size %" G_GSIZE_FORMAT,
806 gst_buffer_get_size (buf));
807
808 ret = gst_pad_push (teletext->srcpad, buf);
809 if (ret != GST_FLOW_OK)
810 goto push_failed;
811
812 return GST_FLOW_OK;
813
814 fetch_page_failed:
815 {
816 GST_ELEMENT_ERROR (teletext, RESOURCE, READ, (NULL), (NULL));
817 return GST_FLOW_ERROR;
818 }
819
820 push_failed:
821 {
822 GST_ERROR_OBJECT (teletext, "Pushing buffer failed, reason %s",
823 gst_flow_get_name (ret));
824 return ret;
825 }
826 }
827
828 static gchar **
gst_teletextdec_vbi_page_to_text_lines(guint start,guint stop,vbi_page * page)829 gst_teletextdec_vbi_page_to_text_lines (guint start, guint stop, vbi_page *
830 page)
831 {
832 const guint lines_count = stop - start + 1;
833 const guint line_length = page->columns;
834 gchar **lines;
835 guint i;
836
837 /* allocate a new NULL-terminated array of strings */
838 lines = (gchar **) g_malloc (sizeof (gchar *) * (lines_count + 1));
839 lines[lines_count] = NULL;
840
841 /* export each line in the range of the teletext page in text format */
842 for (i = start; i <= stop; i++) {
843 lines[i - start] = (gchar *) g_malloc (sizeof (gchar) * (line_length + 1));
844 vbi_print_page_region (page, lines[i - start], line_length + 1, "UTF-8",
845 TRUE, 0, 0, i, line_length, 1);
846 /* Add the null character */
847 lines[i - start][line_length] = '\0';
848 }
849
850 return lines;
851 }
852
853 static GstFlowReturn
gst_teletextdec_export_text_page(GstTeletextDec * teletext,vbi_page * page,GstBuffer ** buf)854 gst_teletextdec_export_text_page (GstTeletextDec * teletext, vbi_page * page,
855 GstBuffer ** buf)
856 {
857 gchar *text;
858 guint size;
859
860 if (teletext->subtitles_mode) {
861 gchar **lines;
862 GString *subs;
863 guint i;
864
865 lines = gst_teletextdec_vbi_page_to_text_lines (1, 23, page);
866 subs = g_string_new ("");
867 /* Strip white spaces and squash blank lines */
868 for (i = 0; i < 23; i++) {
869 g_strstrip (lines[i]);
870 if (g_strcmp0 (lines[i], ""))
871 g_string_append_printf (subs, teletext->subtitles_template, lines[i]);
872 }
873 /* if the page is blank and doesn't contain any line of text, just add a
874 * line break */
875 if (!g_strcmp0 (subs->str, ""))
876 g_string_append (subs, "\n");
877
878 text = subs->str;
879 size = subs->len + 1;
880 g_string_free (subs, FALSE);
881 g_strfreev (lines);
882 } else {
883 size = page->columns * page->rows;
884 text = g_malloc (size);
885 vbi_print_page (page, text, size, "UTF-8", FALSE, TRUE);
886 }
887
888 /* Allocate new buffer */
889 *buf = gst_buffer_new_wrapped (text, size);
890
891 return GST_FLOW_OK;
892 }
893
894 static GstFlowReturn
gst_teletextdec_export_rgba_page(GstTeletextDec * teletext,vbi_page * page,GstBuffer ** buf)895 gst_teletextdec_export_rgba_page (GstTeletextDec * teletext, vbi_page * page,
896 GstBuffer ** buf)
897 {
898 guint size;
899 GstBuffer *lbuf;
900 GstMapInfo buf_map;
901
902 size = teletext->width * teletext->height * sizeof (vbi_rgba);
903
904 /* Allocate new buffer, using the negotiated pool if available. */
905 if (teletext->buf_pool) {
906 GstFlowReturn acquire_rv =
907 gst_buffer_pool_acquire_buffer (teletext->buf_pool, &lbuf, NULL);
908 if (acquire_rv != GST_FLOW_OK) {
909 return acquire_rv;
910 }
911 } else {
912 lbuf = gst_buffer_new_allocate (NULL, size, NULL);
913 if (NULL == lbuf)
914 return GST_FLOW_ERROR;
915 }
916
917 if (!gst_buffer_map (lbuf, &buf_map, GST_MAP_WRITE)) {
918 gst_buffer_unref (lbuf);
919 return GST_FLOW_ERROR;
920 }
921
922 vbi_draw_vt_page (page, VBI_PIXFMT_RGBA32_LE, buf_map.data, FALSE, TRUE);
923 gst_buffer_unmap (lbuf, &buf_map);
924 *buf = lbuf;
925
926 return GST_FLOW_OK;
927 }
928
929 static GstFlowReturn
gst_teletextdec_export_pango_page(GstTeletextDec * teletext,vbi_page * page,GstBuffer ** buf)930 gst_teletextdec_export_pango_page (GstTeletextDec * teletext, vbi_page * page,
931 GstBuffer ** buf)
932 {
933 vbi_char *acp;
934 const guint rows = page->rows;
935 gchar **colors;
936 gchar **lines;
937 GString *subs;
938 guint start, stop, k;
939 gint i, j;
940
941 colors = (gchar **) g_malloc (sizeof (gchar *) * (rows + 1));
942 colors[rows] = NULL;
943
944 /* parse all the lines and approximate it's foreground color using the first
945 * non null character */
946 for (acp = page->text, i = 0; i < page->rows; acp += page->columns, i++) {
947 for (j = 0; j < page->columns; j++) {
948 colors[i] = g_strdup (default_color_map[7]);
949 if (acp[j].unicode != 0x20) {
950 colors[i] = g_strdup (default_color_map[acp[j].foreground]);
951 break;
952 }
953 }
954 }
955
956 /* get an array of strings with each line of the telext page */
957 start = teletext->subtitles_mode ? 1 : 0;
958 stop = teletext->subtitles_mode ? rows - 2 : rows - 1;
959 lines = gst_teletextdec_vbi_page_to_text_lines (start, stop, page);
960
961 /* format each line in pango markup */
962 subs = g_string_new ("");
963 for (k = start; k <= stop; k++) {
964 g_string_append_printf (subs, PANGO_TEMPLATE,
965 teletext->font_description, colors[k], lines[k - start]);
966 }
967
968 /* Allocate new buffer */
969 *buf = gst_buffer_new_wrapped (subs->str, subs->len + 1);
970
971 g_strfreev (lines);
972 g_strfreev (colors);
973 g_string_free (subs, FALSE);
974 return GST_FLOW_OK;
975 }
976
977 /* Converts the line_offset / field_parity byte of a VBI data unit. */
978 static void
gst_teletextdec_lofp_to_line(guint * field,guint * field_line,guint * frame_line,guint lofp,systems system)979 gst_teletextdec_lofp_to_line (guint * field, guint * field_line,
980 guint * frame_line, guint lofp, systems system)
981 {
982 guint line_offset;
983
984 /* field_parity */
985 *field = !(lofp & (1 << 5));
986
987 line_offset = lofp & 31;
988
989 if (line_offset > 0) {
990 static const guint field_start[2][2] = {
991 {0, 263},
992 {0, 313},
993 };
994
995 *field_line = line_offset;
996 *frame_line = field_start[system][*field] + line_offset;
997 } else {
998 *field_line = 0;
999 *frame_line = 0;
1000 }
1001 }
1002
1003 static int
gst_teletextdec_line_address(GstTeletextDec * teletext,GstTeletextFrame * frame,vbi_sliced ** spp,guint lofp,systems system)1004 gst_teletextdec_line_address (GstTeletextDec * teletext,
1005 GstTeletextFrame * frame, vbi_sliced ** spp, guint lofp, systems system)
1006 {
1007 guint field;
1008 guint field_line;
1009 guint frame_line;
1010
1011 if (G_UNLIKELY (frame->current_slice >= frame->sliced_end)) {
1012 GST_LOG_OBJECT (teletext, "Out of sliced VBI buffer space (%d lines).",
1013 (int) (frame->sliced_end - frame->sliced_begin));
1014 return VBI_ERROR;
1015 }
1016
1017 gst_teletextdec_lofp_to_line (&field, &field_line, &frame_line, lofp, system);
1018
1019 GST_LOG_OBJECT (teletext, "Line %u/%u=%u.", field, field_line, frame_line);
1020
1021 if (frame_line != 0) {
1022 GST_LOG_OBJECT (teletext, "Last frame Line %u.", frame->last_frame_line);
1023 if (frame_line <= frame->last_frame_line) {
1024 GST_LOG_OBJECT (teletext, "New frame");
1025 return VBI_NEW_FRAME;
1026 }
1027
1028 /* FIXME : This never happens, since lofp is a guint8 */
1029 #if 0
1030 /* new segment flag */
1031 if (lofp < 0) {
1032 GST_LOG_OBJECT (teletext, "New frame");
1033 return VBI_NEW_FRAME;
1034 }
1035 #endif
1036
1037 frame->last_field = field;
1038 frame->last_field_line = field_line;
1039 frame->last_frame_line = frame_line;
1040
1041 *spp = frame->current_slice++;
1042 (*spp)->line = frame_line;
1043 } else {
1044 /* Undefined line. */
1045 return VBI_ERROR;
1046 }
1047
1048 return VBI_SUCCESS;
1049 }
1050
1051 static gboolean
gst_teletextdec_extract_data_units(GstTeletextDec * teletext,GstTeletextFrame * f,const guint8 * packet,guint * offset,gsize size)1052 gst_teletextdec_extract_data_units (GstTeletextDec * teletext,
1053 GstTeletextFrame * f, const guint8 * packet, guint * offset, gsize size)
1054 {
1055 const guint8 *data_unit;
1056 guint i;
1057
1058 while (*offset < size) {
1059 vbi_sliced *s = NULL;
1060 gint data_unit_id, data_unit_length;
1061
1062 data_unit = packet + *offset;
1063 data_unit_id = data_unit[0];
1064 data_unit_length = data_unit[1];
1065 GST_LOG_OBJECT (teletext, "vbi header %02x %02x %02x\n", data_unit[0],
1066 data_unit[1], data_unit[2]);
1067
1068 switch (data_unit_id) {
1069 case DATA_UNIT_STUFFING:
1070 {
1071 *offset += 2 + data_unit_length;
1072 break;
1073 }
1074
1075 case DATA_UNIT_EBU_TELETEXT_NON_SUBTITLE:
1076 case DATA_UNIT_EBU_TELETEXT_SUBTITLE:
1077 {
1078 gint res;
1079
1080 if (G_UNLIKELY (data_unit_length != 1 + 1 + 42)) {
1081 /* Skip this data unit */
1082 GST_WARNING_OBJECT (teletext, "The data unit length is not 44 bytes");
1083 *offset += 2 + data_unit_length;
1084 break;
1085 }
1086
1087 res =
1088 gst_teletextdec_line_address (teletext, f, &s, data_unit[2],
1089 SYSTEM_625);
1090 if (G_UNLIKELY (res == VBI_ERROR)) {
1091 /* Can't retrieve line address, skip this data unit */
1092 GST_WARNING_OBJECT (teletext,
1093 "Could not retrieve line address for this data unit");
1094 return VBI_ERROR;
1095 }
1096 if (G_UNLIKELY (f->last_field_line > 0
1097 && (f->last_field_line - 7 >= 23 - 7))) {
1098 GST_WARNING_OBJECT (teletext, "Bad line: %d", f->last_field_line - 7);
1099 return VBI_ERROR;
1100 }
1101 if (res == VBI_NEW_FRAME) {
1102 /* New frame */
1103 return VBI_NEW_FRAME;
1104 }
1105 s->id = VBI_SLICED_TELETEXT_B;
1106 for (i = 0; i < 42; i++)
1107 s->data[i] = vbi_rev8 (data_unit[4 + i]);
1108 *offset += 46;
1109 break;
1110 }
1111
1112 case DATA_UNIT_ZVBI_WSS_CPR1204:
1113 case DATA_UNIT_ZVBI_CLOSED_CAPTION_525:
1114 case DATA_UNIT_ZVBI_MONOCHROME_SAMPLES_525:
1115 case DATA_UNIT_VPS:
1116 case DATA_UNIT_WSS:
1117 case DATA_UNIT_CLOSED_CAPTION:
1118 case DATA_UNIT_MONOCHROME_SAMPLES:
1119 {
1120 /*Not supported yet */
1121 *offset += 2 + data_unit_length;
1122 break;
1123 }
1124
1125 default:
1126 {
1127 /* corrupted stream, increase the offset by one until we sync */
1128 GST_LOG_OBJECT (teletext, "Corrupted, increasing offset by one");
1129 *offset += 1;
1130 break;
1131 }
1132 }
1133 }
1134 return VBI_SUCCESS;
1135 }
1136
1137 static gboolean
teletext_init(GstPlugin * teletext)1138 teletext_init (GstPlugin * teletext)
1139 {
1140 GST_DEBUG_CATEGORY_INIT (gst_teletextdec_debug, "teletext", 0,
1141 "Teletext decoder");
1142 return gst_element_register (teletext, "teletextdec", GST_RANK_NONE,
1143 GST_TYPE_TELETEXTDEC);
1144 }
1145
1146 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1147 GST_VERSION_MINOR,
1148 teletext,
1149 "Teletext plugin",
1150 teletext_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1151