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