2010-09-13 09:59:18 +00:00
|
|
|
/*
|
|
|
|
* GStreamer
|
2011-05-23 13:32:09 +00:00
|
|
|
* Copyright (C) 2009 Sebastian Pölsterl <sebp@k-d-w.org>
|
2010-09-13 09:59:18 +00:00
|
|
|
* Copyright (C) 2010 Andoni Morales Alastruey <ylatuya@gmail.com>
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
* Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* SECTION:element-teletextdec
|
|
|
|
*
|
|
|
|
* Decode PES stream containing teletext information to RGBA stream
|
|
|
|
*
|
|
|
|
* <refsect2>
|
|
|
|
* <title>Example launch line</title>
|
|
|
|
* |[
|
|
|
|
* gst-launch -v -m filesrc location=recording.mpeg ! mpegtsdemux ! private/teletext ! teletextdec ! ffmpegcolorspace ! ximagesink
|
|
|
|
* ]|
|
|
|
|
* </refsect2>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <gst/gst.h>
|
|
|
|
#include <gst/video/video.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "gstteletextdec.h"
|
|
|
|
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_teletextdec_debug);
|
|
|
|
#define GST_CAT_DEFAULT gst_teletextdec_debug
|
|
|
|
|
|
|
|
#define SUBTITLES_PAGE 888
|
|
|
|
#define MAX_SLICES 32
|
2010-09-10 12:34:42 +00:00
|
|
|
#define DEFAULT_FONT_DESCRIPTION "verdana 12"
|
|
|
|
#define PANGO_TEMPLATE "<span font_desc=\"%s\" foreground=\"%s\"> %s \n</span>"
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
/* Filter signals and args */
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
LAST_SIGNAL
|
|
|
|
};
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
PROP_0,
|
|
|
|
PROP_PAGENO,
|
|
|
|
PROP_SUBNO,
|
|
|
|
PROP_SUBTITLES_MODE,
|
2010-09-10 12:34:42 +00:00
|
|
|
PROP_SUBS_TEMPLATE,
|
|
|
|
PROP_FONT_DESCRIPTION
|
2010-09-13 09:59:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
VBI_ERROR = -1,
|
|
|
|
VBI_SUCCESS = 0,
|
|
|
|
VBI_NEW_FRAME = 1
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef enum
|
|
|
|
{
|
|
|
|
DATA_UNIT_EBU_TELETEXT_NON_SUBTITLE = 0x02,
|
|
|
|
DATA_UNIT_EBU_TELETEXT_SUBTITLE = 0x03,
|
|
|
|
DATA_UNIT_EBU_TELETEXT_INVERTED = 0x0C,
|
|
|
|
|
|
|
|
DATA_UNIT_ZVBI_WSS_CPR1204 = 0xB4,
|
|
|
|
DATA_UNIT_ZVBI_CLOSED_CAPTION_525 = 0xB5,
|
|
|
|
DATA_UNIT_ZVBI_MONOCHROME_SAMPLES_525 = 0xB6,
|
|
|
|
|
|
|
|
DATA_UNIT_VPS = 0xC3,
|
|
|
|
DATA_UNIT_WSS = 0xC4,
|
|
|
|
DATA_UNIT_CLOSED_CAPTION = 0xC5,
|
|
|
|
DATA_UNIT_MONOCHROME_SAMPLES = 0xC6,
|
|
|
|
|
|
|
|
DATA_UNIT_STUFFING = 0xFF,
|
|
|
|
} data_unit_id;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
int pgno;
|
|
|
|
int subno;
|
|
|
|
} page_info;
|
|
|
|
|
|
|
|
typedef enum
|
|
|
|
{
|
|
|
|
SYSTEM_525 = 0,
|
|
|
|
SYSTEM_625
|
|
|
|
} systems;
|
|
|
|
|
2010-09-10 12:34:42 +00:00
|
|
|
/*
|
|
|
|
* ETS 300 706 Table 30: Colour Map
|
|
|
|
*/
|
|
|
|
static const gchar *default_color_map[40] = {
|
|
|
|
"#000000", "#FF0000", "#00FF00", "#FFFF00", "#0000FF",
|
|
|
|
"#FF00FF", "#00FFFF", "#FFFFFF", "#000000", "#770000",
|
|
|
|
"#007700", "#777700", "#000077", "#770077", "#007777",
|
|
|
|
"#777777", "#FF0055", "#FF7700", "#00FF77", "#FFFFBB",
|
|
|
|
"#00CCAA", "#550000", "#665522", "#CC7777", "#333333",
|
|
|
|
"#FF7777", "#77FF77", "#FFFF77", "#7777FF", "#FF77FF",
|
|
|
|
"#77FFFF", "#DDD0DD",
|
|
|
|
|
|
|
|
/* Private colors */
|
|
|
|
"#000000", "#FFAA99", "#44EE00", "#FFDD00", "#FFAA99",
|
|
|
|
"#FF00FF", "#00FFFF", "#EEEEEE"
|
|
|
|
};
|
|
|
|
|
2010-09-13 09:59:18 +00:00
|
|
|
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
|
|
GST_PAD_SINK,
|
|
|
|
GST_PAD_ALWAYS,
|
|
|
|
GST_STATIC_CAPS
|
|
|
|
("video/mpeg,mpegversion=2,systemstream=TRUE ; private/teletext")
|
|
|
|
);
|
|
|
|
|
|
|
|
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
|
|
|
GST_PAD_SRC,
|
|
|
|
GST_PAD_ALWAYS,
|
2010-06-10 10:44:27 +00:00
|
|
|
GST_STATIC_CAPS
|
|
|
|
(GST_VIDEO_CAPS_RGBA "; text/plain ; text/html ; text/x-pango-markup")
|
2010-09-13 09:59:18 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
/* debug category for filtering log messages */
|
|
|
|
#define DEBUG_INIT(bla) \
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_teletextdec_debug, "teletext", 0, "Teletext decoder");
|
|
|
|
|
|
|
|
GST_BOILERPLATE_FULL (GstTeletextDec, gst_teletextdec, GstElement,
|
|
|
|
GST_TYPE_ELEMENT, DEBUG_INIT);
|
|
|
|
|
|
|
|
static void gst_teletextdec_set_property (GObject * object, guint prop_id,
|
|
|
|
const GValue * value, GParamSpec * pspec);
|
|
|
|
static void gst_teletextdec_get_property (GObject * object, guint prop_id,
|
|
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static void gst_teletextdec_finalize (GObject * object);
|
|
|
|
|
|
|
|
static GstStateChangeReturn gst_teletextdec_change_state (GstElement * element,
|
|
|
|
GstStateChange transition);
|
|
|
|
|
|
|
|
static GstFlowReturn gst_teletextdec_chain (GstPad * pad, GstBuffer * buf);
|
|
|
|
static gboolean gst_teletextdec_sink_setcaps (GstPad * pad, GstCaps * caps);
|
|
|
|
static gboolean gst_teletextdec_sink_event (GstPad * pad, GstEvent * event);
|
|
|
|
static GstPadLinkReturn gst_teletextdec_src_set_caps (GstPad * pad,
|
|
|
|
GstCaps * caps);
|
|
|
|
|
|
|
|
static vbi_bool gst_teletextdec_convert (vbi_dvb_demux * dx, gpointer user_data,
|
|
|
|
const vbi_sliced * sliced, guint n_lines, gint64 pts);
|
|
|
|
static void gst_teletextdec_event_handler (vbi_event * ev, void *user_data);
|
|
|
|
|
|
|
|
static GstFlowReturn gst_teletextdec_push_page (GstTeletextDec * teletext);
|
|
|
|
static GstFlowReturn gst_teletextdec_export_text_page (GstTeletextDec *
|
|
|
|
teletext, vbi_page * page, GstBuffer ** buf);
|
|
|
|
static GstFlowReturn gst_teletextdec_export_html_page (GstTeletextDec *
|
|
|
|
teletext, vbi_page * page, GstBuffer ** buf);
|
|
|
|
static GstFlowReturn gst_teletextdec_export_rgba_page (GstTeletextDec *
|
|
|
|
teletext, vbi_page * page, GstBuffer ** buf);
|
2010-06-10 10:44:27 +00:00
|
|
|
static GstFlowReturn gst_teletextdec_export_pango_page (GstTeletextDec *
|
|
|
|
teletext, vbi_page * page, GstBuffer ** buf);
|
|
|
|
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
static gboolean gst_teletextdec_push_preroll_buffer (GstTeletextDec * teletext);
|
|
|
|
static void gst_teletextdec_process_telx_buffer (GstTeletextDec * teletext,
|
|
|
|
GstBuffer * buf);
|
|
|
|
static void gst_teletextdec_process_pes_buffer (GstTeletextDec * teletext,
|
|
|
|
GstBuffer * buf);
|
|
|
|
static gboolean gst_teletextdec_extract_data_units (GstTeletextDec * teletext,
|
|
|
|
GstTeletextFrame * f, guint8 * packet, guint * offset, gint size);
|
|
|
|
|
|
|
|
static void gst_teletextdec_zvbi_init (GstTeletextDec * teletext);
|
|
|
|
static void gst_teletextdec_zvbi_clear (GstTeletextDec * teletext);
|
|
|
|
|
|
|
|
/* GObject vmethod implementations */
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_base_init (gpointer klass)
|
|
|
|
{
|
|
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
|
|
|
|
|
|
gst_element_class_set_details_simple (element_class,
|
|
|
|
"Teletext decoder",
|
|
|
|
"Decoder",
|
|
|
|
"Decode PES or raw VBI stream containing teletext information to RGBA, HTML and text",
|
|
|
|
"Sebastian Pölsterl <sebp@k-d-w.org>, "
|
|
|
|
"Andoni Morales Alastruey <ylatuya@gmail.com>");
|
|
|
|
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
|
|
gst_static_pad_template_get (&src_template));
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
|
|
gst_static_pad_template_get (&sink_template));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the gstteletext's class */
|
|
|
|
static void
|
|
|
|
gst_teletextdec_class_init (GstTeletextDecClass * klass)
|
|
|
|
{
|
|
|
|
GObjectClass *gobject_class;
|
|
|
|
GstElementClass *gstelement_class;
|
|
|
|
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->set_property = gst_teletextdec_set_property;
|
|
|
|
gobject_class->get_property = gst_teletextdec_get_property;
|
|
|
|
gobject_class->finalize = gst_teletextdec_finalize;
|
|
|
|
|
|
|
|
gstelement_class = GST_ELEMENT_CLASS (klass);
|
2011-05-23 13:32:09 +00:00
|
|
|
gstelement_class->change_state =
|
|
|
|
GST_DEBUG_FUNCPTR (gst_teletextdec_change_state);
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
g_object_class_install_property (gobject_class, PROP_PAGENO,
|
|
|
|
g_param_spec_int ("page", "Page number",
|
|
|
|
"Number of page that should displayed",
|
2011-05-23 13:32:09 +00:00
|
|
|
100, 999, 100, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SUBNO,
|
|
|
|
g_param_spec_int ("subpage", "Sub-page number",
|
|
|
|
"Number of sub-page that should displayed (-1 for all)",
|
2011-05-23 13:32:09 +00:00
|
|
|
-1, 0x99, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SUBTITLES_MODE,
|
|
|
|
g_param_spec_boolean ("subtitles-mode", "Enable subtitles mode",
|
|
|
|
"Enables subtitles mode for text output stripping the blank lines and "
|
2011-05-23 13:32:09 +00:00
|
|
|
"the teletext state lines", FALSE,
|
|
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SUBS_TEMPLATE,
|
|
|
|
g_param_spec_string ("subtitles-template", "Subtitles output template",
|
|
|
|
"Output template used to print each one of the subtitles lines",
|
2011-05-23 13:32:09 +00:00
|
|
|
g_strescape ("%s\n", NULL),
|
|
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
2010-09-10 12:34:42 +00:00
|
|
|
|
|
|
|
g_object_class_install_property (gobject_class, PROP_FONT_DESCRIPTION,
|
|
|
|
g_param_spec_string ("font-description", "Pango font description",
|
|
|
|
"Font description used for the pango output.",
|
2011-05-23 13:32:09 +00:00
|
|
|
DEFAULT_FONT_DESCRIPTION,
|
|
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
2010-09-13 09:59:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the new element
|
|
|
|
* initialize instance structure
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
gst_teletextdec_init (GstTeletextDec * teletext, GstTeletextDecClass * klass)
|
|
|
|
{
|
|
|
|
/* Create sink pad */
|
|
|
|
teletext->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
|
|
|
|
gst_pad_set_setcaps_function (teletext->sinkpad,
|
|
|
|
GST_DEBUG_FUNCPTR (gst_teletextdec_sink_setcaps));
|
|
|
|
gst_pad_set_chain_function (teletext->sinkpad,
|
|
|
|
GST_DEBUG_FUNCPTR (gst_teletextdec_chain));
|
|
|
|
gst_pad_set_event_function (teletext->sinkpad,
|
|
|
|
GST_DEBUG_FUNCPTR (gst_teletextdec_sink_event));
|
|
|
|
gst_element_add_pad (GST_ELEMENT (teletext), teletext->sinkpad);
|
|
|
|
|
|
|
|
/* Create src pad */
|
|
|
|
teletext->srcpad = gst_pad_new_from_static_template (&src_template, "src");
|
|
|
|
gst_pad_set_setcaps_function (teletext->srcpad,
|
|
|
|
GST_DEBUG_FUNCPTR (gst_teletextdec_src_set_caps));
|
|
|
|
gst_element_add_pad (GST_ELEMENT (teletext), teletext->srcpad);
|
|
|
|
|
|
|
|
teletext->demux = NULL;
|
|
|
|
teletext->decoder = NULL;
|
|
|
|
teletext->pageno = 0x100;
|
|
|
|
teletext->subno = -1;
|
|
|
|
teletext->subtitles_mode = FALSE;
|
|
|
|
teletext->subtitles_template = g_strescape ("%s\n", NULL);
|
2010-09-10 12:34:42 +00:00
|
|
|
teletext->font_description = g_strdup (DEFAULT_FONT_DESCRIPTION);
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
teletext->in_timestamp = GST_CLOCK_TIME_NONE;
|
|
|
|
teletext->in_duration = GST_CLOCK_TIME_NONE;
|
|
|
|
|
|
|
|
teletext->rate_numerator = 0;
|
|
|
|
teletext->rate_denominator = 1;
|
|
|
|
|
|
|
|
teletext->queue = NULL;
|
|
|
|
teletext->queue_lock = g_mutex_new ();
|
|
|
|
|
|
|
|
teletext->frame = g_new0 (GstTeletextFrame, 1);
|
|
|
|
teletext->frame->sliced_begin = g_new (vbi_sliced, MAX_SLICES);
|
2012-06-18 09:36:15 +00:00
|
|
|
teletext->frame->current_slice = teletext->frame->sliced_begin;
|
|
|
|
teletext->frame->sliced_end = teletext->frame->sliced_begin + MAX_SLICES;
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
teletext->last_ts = 0;
|
|
|
|
|
|
|
|
teletext->process_buf_func = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_finalize (GObject * object)
|
|
|
|
{
|
|
|
|
GstTeletextDec *teletext = GST_TELETEXTDEC (object);
|
|
|
|
|
|
|
|
g_mutex_free (teletext->queue_lock);
|
|
|
|
|
|
|
|
g_free (teletext->frame);
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_zvbi_init (GstTeletextDec * teletext)
|
|
|
|
{
|
|
|
|
g_return_if_fail (teletext != NULL);
|
|
|
|
|
|
|
|
GST_LOG_OBJECT (teletext, "Initializing structures");
|
|
|
|
|
|
|
|
teletext->decoder = vbi_decoder_new ();
|
|
|
|
|
|
|
|
vbi_event_handler_register (teletext->decoder,
|
|
|
|
VBI_EVENT_TTX_PAGE | VBI_EVENT_CAPTION,
|
|
|
|
gst_teletextdec_event_handler, teletext);
|
|
|
|
|
|
|
|
g_mutex_lock (teletext->queue_lock);
|
|
|
|
teletext->queue = g_queue_new ();
|
|
|
|
g_mutex_unlock (teletext->queue_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_zvbi_clear (GstTeletextDec * teletext)
|
|
|
|
{
|
|
|
|
g_return_if_fail (teletext != NULL);
|
|
|
|
|
|
|
|
GST_LOG_OBJECT (teletext, "Clearing structures");
|
|
|
|
|
|
|
|
if (teletext->demux != NULL) {
|
|
|
|
vbi_dvb_demux_delete (teletext->demux);
|
|
|
|
teletext->demux = NULL;
|
|
|
|
}
|
|
|
|
if (teletext->decoder != NULL) {
|
|
|
|
vbi_decoder_delete (teletext->decoder);
|
|
|
|
teletext->decoder = NULL;
|
|
|
|
}
|
|
|
|
if (teletext->frame != NULL) {
|
|
|
|
g_free (teletext->frame);
|
|
|
|
teletext->frame = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_mutex_lock (teletext->queue_lock);
|
|
|
|
if (teletext->queue != NULL) {
|
|
|
|
g_queue_free (teletext->queue);
|
|
|
|
teletext->queue = NULL;
|
|
|
|
}
|
|
|
|
g_mutex_unlock (teletext->queue_lock);
|
|
|
|
|
|
|
|
teletext->in_timestamp = GST_CLOCK_TIME_NONE;
|
|
|
|
teletext->in_duration = GST_CLOCK_TIME_NONE;
|
|
|
|
teletext->pageno = 0x100;
|
|
|
|
teletext->subno = -1;
|
|
|
|
teletext->last_ts = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_set_property (GObject * object, guint prop_id,
|
|
|
|
const GValue * value, GParamSpec * pspec)
|
|
|
|
{
|
|
|
|
GstTeletextDec *teletext = GST_TELETEXTDEC (object);
|
|
|
|
|
|
|
|
switch (prop_id) {
|
|
|
|
case PROP_PAGENO:
|
|
|
|
teletext->pageno = (gint) vbi_bin2bcd (g_value_get_int (value));
|
|
|
|
break;
|
|
|
|
case PROP_SUBNO:
|
|
|
|
teletext->subno = g_value_get_int (value);
|
|
|
|
break;
|
|
|
|
case PROP_SUBTITLES_MODE:
|
|
|
|
teletext->subtitles_mode = g_value_get_boolean (value);
|
|
|
|
break;
|
|
|
|
case PROP_SUBS_TEMPLATE:
|
|
|
|
teletext->subtitles_template = g_value_dup_string (value);
|
|
|
|
break;
|
2010-09-10 12:34:42 +00:00
|
|
|
case PROP_FONT_DESCRIPTION:
|
|
|
|
teletext->font_description = g_value_dup_string (value);
|
|
|
|
break;
|
2010-09-13 09:59:18 +00:00
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_get_property (GObject * object, guint prop_id,
|
|
|
|
GValue * value, GParamSpec * pspec)
|
|
|
|
{
|
|
|
|
GstTeletextDec *teletext = GST_TELETEXTDEC (object);
|
|
|
|
|
|
|
|
switch (prop_id) {
|
|
|
|
case PROP_PAGENO:
|
|
|
|
g_value_set_int (value, (gint) vbi_bcd2dec (teletext->pageno));
|
|
|
|
break;
|
|
|
|
case PROP_SUBNO:
|
|
|
|
g_value_set_int (value, teletext->subno);
|
|
|
|
break;
|
|
|
|
case PROP_SUBTITLES_MODE:
|
|
|
|
g_value_set_boolean (value, teletext->subtitles_mode);
|
|
|
|
break;
|
|
|
|
case PROP_SUBS_TEMPLATE:
|
|
|
|
g_value_set_string (value, teletext->subtitles_template);
|
|
|
|
break;
|
2010-09-10 12:34:42 +00:00
|
|
|
case PROP_FONT_DESCRIPTION:
|
|
|
|
g_value_set_string (value, teletext->font_description);
|
|
|
|
break;
|
2010-09-13 09:59:18 +00:00
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_teletextdec_sink_event (GstPad * pad, GstEvent * event)
|
|
|
|
{
|
|
|
|
gboolean ret;
|
|
|
|
GstTeletextDec *teletext = GST_TELETEXTDEC (gst_pad_get_parent (pad));
|
|
|
|
|
|
|
|
GST_DEBUG_OBJECT (teletext, "got event %s",
|
|
|
|
gst_event_type_get_name (GST_EVENT_TYPE (event)));
|
|
|
|
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
|
|
case GST_EVENT_NEWSEGMENT:
|
|
|
|
/* maybe save and/or update the current segment (e.g. for output
|
|
|
|
* clipping) or convert the event into one in a different format
|
|
|
|
* (e.g. BYTES to TIME) or drop it and set a flag to send a newsegment
|
|
|
|
* event in a different format later */
|
|
|
|
ret = gst_pad_push_event (teletext->srcpad, event);
|
|
|
|
break;
|
|
|
|
case GST_EVENT_EOS:
|
|
|
|
/* end-of-stream, we should close down all stream leftovers here */
|
|
|
|
gst_teletextdec_zvbi_clear (teletext);
|
|
|
|
ret = gst_pad_push_event (teletext->srcpad, event);
|
|
|
|
break;
|
|
|
|
case GST_EVENT_FLUSH_STOP:
|
|
|
|
gst_teletextdec_zvbi_clear (teletext);
|
|
|
|
gst_teletextdec_zvbi_init (teletext);
|
|
|
|
ret = gst_pad_push_event (teletext->srcpad, event);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = gst_pad_event_default (pad, event);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
gst_object_unref (teletext);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GstStateChangeReturn
|
|
|
|
gst_teletextdec_change_state (GstElement * element, GstStateChange transition)
|
|
|
|
{
|
|
|
|
GstStateChangeReturn ret;
|
|
|
|
GstTeletextDec *teletext;
|
|
|
|
|
|
|
|
teletext = GST_TELETEXTDEC (element);
|
|
|
|
|
|
|
|
switch (transition) {
|
|
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
|
|
gst_teletextdec_zvbi_init (teletext);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
if (ret != GST_STATE_CHANGE_SUCCESS)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
switch (transition) {
|
|
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
|
|
gst_teletextdec_zvbi_clear (teletext);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_teletextdec_sink_setcaps (GstPad * pad, GstCaps * caps)
|
|
|
|
{
|
|
|
|
GstTeletextDec *teletext = GST_TELETEXTDEC (gst_pad_get_parent (pad));
|
|
|
|
GstStructure *structure = gst_caps_get_structure (caps, 0);
|
|
|
|
const gchar *mimetype = gst_structure_get_name (structure);
|
|
|
|
|
|
|
|
GST_DEBUG_OBJECT (teletext, "%s:%s, caps=%" GST_PTR_FORMAT,
|
|
|
|
GST_DEBUG_PAD_NAME (pad), caps);
|
|
|
|
|
|
|
|
if (g_strcmp0 (mimetype, "private/teletext") == 0) {
|
|
|
|
teletext->process_buf_func = gst_teletextdec_process_telx_buffer;
|
|
|
|
goto accept_caps;
|
|
|
|
} else if (g_strcmp0 (mimetype, "video/mpeg") == 0) {
|
|
|
|
gint version;
|
|
|
|
gboolean is_systemstream;
|
|
|
|
|
|
|
|
if (!gst_structure_get_int (structure, "mpegversion", &version) ||
|
|
|
|
!gst_structure_get_boolean (structure, "systemstream",
|
|
|
|
&is_systemstream))
|
|
|
|
goto refuse_caps;
|
|
|
|
|
|
|
|
if (version != 2 || !is_systemstream)
|
|
|
|
goto refuse_caps;
|
|
|
|
|
|
|
|
teletext->process_buf_func = gst_teletextdec_process_pes_buffer;
|
|
|
|
teletext->demux = vbi_dvb_pes_demux_new (gst_teletextdec_convert, teletext);
|
|
|
|
goto accept_caps;
|
|
|
|
} else
|
|
|
|
goto refuse_caps;
|
|
|
|
|
|
|
|
accept_caps:
|
|
|
|
{
|
|
|
|
gst_object_unref (teletext);
|
|
|
|
return gst_teletextdec_push_preroll_buffer (teletext);
|
|
|
|
}
|
|
|
|
|
|
|
|
refuse_caps:
|
|
|
|
{
|
|
|
|
GST_ERROR_OBJECT (teletext,
|
|
|
|
"pad %s refused renegotiation to %" GST_PTR_FORMAT,
|
|
|
|
GST_PAD_NAME (pad), caps);
|
|
|
|
gst_object_unref (teletext);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_teletextdec_src_set_caps (GstPad * pad, GstCaps * caps)
|
|
|
|
{
|
|
|
|
GstTeletextDec *teletext;
|
|
|
|
GstStructure *structure = NULL;
|
|
|
|
const gchar *mimetype;
|
2011-05-23 13:32:09 +00:00
|
|
|
GstPad *peer;
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
teletext = GST_TELETEXTDEC (gst_pad_get_parent (pad));
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Linking teletext source pad");
|
|
|
|
|
|
|
|
if (gst_caps_is_empty (caps)) {
|
|
|
|
GST_ERROR_OBJECT (teletext,
|
|
|
|
"pad %s refused renegotiation to %" GST_PTR_FORMAT,
|
|
|
|
GST_PAD_NAME (pad), caps);
|
|
|
|
goto refuse_caps;
|
|
|
|
}
|
|
|
|
|
2011-05-23 13:32:09 +00:00
|
|
|
peer = gst_pad_get_peer (pad);
|
|
|
|
if (peer) {
|
|
|
|
gst_pad_set_caps (peer, caps);
|
|
|
|
gst_object_unref (peer);
|
|
|
|
}
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
|
|
mimetype = gst_structure_get_name (structure);
|
|
|
|
|
|
|
|
if (g_strcmp0 (mimetype, "video/x-raw-rgb") == 0) {
|
|
|
|
teletext->output_format = GST_TELETEXTDEC_OUTPUT_FORMAT_RGBA;
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Selected RGBA output format");
|
|
|
|
} else if (g_strcmp0 (mimetype, "text/html") == 0) {
|
|
|
|
teletext->output_format = GST_TELETEXTDEC_OUTPUT_FORMAT_HTML;
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Selected HTML output format");
|
|
|
|
} else if (g_strcmp0 (mimetype, "text/plain") == 0) {
|
|
|
|
teletext->output_format = GST_TELETEXTDEC_OUTPUT_FORMAT_TEXT;
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Selected text output format");
|
2010-06-10 10:44:27 +00:00
|
|
|
} else if (g_strcmp0 (mimetype, "text/x-pango-markup") == 0) {
|
|
|
|
teletext->output_format = GST_TELETEXTDEC_OUTPUT_FORMAT_PANGO;
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Selected pango markup output format");
|
2010-09-13 09:59:18 +00:00
|
|
|
} else
|
|
|
|
goto refuse_caps;
|
|
|
|
|
|
|
|
gst_object_unref (teletext);
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
|
|
|
|
refuse_caps:
|
|
|
|
{
|
|
|
|
gst_object_unref (teletext);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_reset_frame (GstTeletextDec * teletext)
|
|
|
|
{
|
|
|
|
teletext->frame->current_slice = teletext->frame->sliced_begin;
|
|
|
|
teletext->frame->sliced_end = teletext->frame->sliced_begin + MAX_SLICES;
|
|
|
|
teletext->frame->last_field = 0;
|
|
|
|
teletext->frame->last_field_line = 0;
|
|
|
|
teletext->frame->last_frame_line = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_process_pes_buffer (GstTeletextDec * teletext, GstBuffer * buf)
|
|
|
|
{
|
|
|
|
vbi_dvb_demux_feed (teletext->demux, GST_BUFFER_DATA (buf),
|
|
|
|
GST_BUFFER_SIZE (buf));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_process_telx_buffer (GstTeletextDec * teletext, GstBuffer * buf)
|
|
|
|
{
|
|
|
|
guint8 *data = GST_BUFFER_DATA (buf);
|
|
|
|
const gint size = GST_BUFFER_SIZE (buf);
|
|
|
|
guint offset = 0;
|
|
|
|
gint res;
|
|
|
|
|
|
|
|
teletext->in_timestamp = GST_BUFFER_TIMESTAMP (buf);
|
|
|
|
teletext->in_duration = GST_BUFFER_DURATION (buf);
|
|
|
|
|
|
|
|
if (teletext->frame == NULL) {
|
|
|
|
gst_teletextdec_reset_frame (teletext);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (offset < size) {
|
|
|
|
res =
|
|
|
|
gst_teletextdec_extract_data_units (teletext, teletext->frame, data,
|
|
|
|
&offset, size);
|
|
|
|
|
|
|
|
if (res == VBI_NEW_FRAME) {
|
|
|
|
/* We have a new frame, it's time to feed the decoder */
|
|
|
|
vbi_sliced *s;
|
|
|
|
gint n_lines;
|
|
|
|
|
|
|
|
n_lines = teletext->frame->current_slice - teletext->frame->sliced_begin;
|
|
|
|
GST_LOG_OBJECT (teletext, "Completed frame, decoding new %d lines",
|
|
|
|
n_lines);
|
|
|
|
s = g_memdup (teletext->frame->sliced_begin,
|
|
|
|
n_lines * sizeof (vbi_sliced));
|
|
|
|
vbi_decode (teletext->decoder, s, n_lines, teletext->last_ts);
|
|
|
|
/* From vbi_decode():
|
|
|
|
* timestamp shall advance by 1/30 to 1/25 seconds whenever calling this
|
|
|
|
* function. Failure to do so will be interpreted as frame dropping, which
|
|
|
|
* starts a resynchronization cycle, eventually a channel switch may be assumed
|
|
|
|
* which resets even more decoder state. So even if a frame did not contain
|
|
|
|
* any useful data this function must be called, with lines set to zero.
|
|
|
|
*/
|
|
|
|
teletext->last_ts += 0.04;
|
|
|
|
|
|
|
|
g_free (s);
|
|
|
|
gst_teletextdec_reset_frame (teletext);
|
|
|
|
} else if (res == VBI_ERROR) {
|
|
|
|
gst_teletextdec_reset_frame (teletext);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static vbi_bool
|
|
|
|
gst_teletextdec_convert (vbi_dvb_demux * dx,
|
|
|
|
gpointer user_data, const vbi_sliced * sliced, guint n_lines, gint64 pts)
|
|
|
|
{
|
|
|
|
gdouble sample_time;
|
|
|
|
vbi_sliced *s;
|
|
|
|
|
|
|
|
GstTeletextDec *teletext = GST_TELETEXTDEC (user_data);
|
|
|
|
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Converting %u lines to decode", n_lines);
|
|
|
|
|
|
|
|
sample_time = pts * (1 / 90000.0);
|
|
|
|
|
|
|
|
s = g_memdup (sliced, n_lines * sizeof (vbi_sliced));
|
|
|
|
vbi_decode (teletext->decoder, s, n_lines, sample_time);
|
|
|
|
g_free (s);
|
|
|
|
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_teletextdec_event_handler (vbi_event * ev, void *user_data)
|
|
|
|
{
|
|
|
|
page_info *pi;
|
|
|
|
vbi_pgno pgno;
|
|
|
|
vbi_subno subno;
|
|
|
|
|
|
|
|
GstTeletextDec *teletext = GST_TELETEXTDEC (user_data);
|
|
|
|
|
|
|
|
switch (ev->type) {
|
|
|
|
case VBI_EVENT_TTX_PAGE:
|
|
|
|
pgno = ev->ev.ttx_page.pgno;
|
|
|
|
subno = ev->ev.ttx_page.subno;
|
|
|
|
|
|
|
|
if (pgno != teletext->pageno
|
|
|
|
|| (teletext->subno != -1 && subno != teletext->subno))
|
|
|
|
return;
|
|
|
|
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Received teletext page %03d.%02d",
|
|
|
|
(gint) vbi_bcd2dec (pgno), (gint) vbi_bcd2dec (subno));
|
|
|
|
|
|
|
|
pi = g_new (page_info, 1);
|
|
|
|
pi->pgno = pgno;
|
|
|
|
pi->subno = subno;
|
|
|
|
|
|
|
|
g_mutex_lock (teletext->queue_lock);
|
|
|
|
g_queue_push_tail (teletext->queue, pi);
|
|
|
|
g_mutex_unlock (teletext->queue_lock);
|
|
|
|
break;
|
|
|
|
case VBI_EVENT_CAPTION:
|
|
|
|
/* TODO: Handle subtitles in caption teletext pages */
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Received caption page. Not implemented");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* this function does the actual processing
|
|
|
|
*/
|
|
|
|
static GstFlowReturn
|
|
|
|
gst_teletextdec_chain (GstPad * pad, GstBuffer * buf)
|
|
|
|
{
|
|
|
|
GstTeletextDec *teletext = GST_TELETEXTDEC (GST_PAD_PARENT (pad));
|
|
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
|
|
|
|
teletext->in_timestamp = GST_BUFFER_TIMESTAMP (buf);
|
|
|
|
teletext->in_duration = GST_BUFFER_DURATION (buf);
|
|
|
|
|
|
|
|
teletext->process_buf_func (teletext, buf);
|
|
|
|
gst_buffer_unref (buf);
|
|
|
|
|
|
|
|
g_mutex_lock (teletext->queue_lock);
|
|
|
|
if (!g_queue_is_empty (teletext->queue)) {
|
|
|
|
ret = gst_teletextdec_push_page (teletext);
|
2010-06-10 11:55:30 +00:00
|
|
|
if (ret != GST_FLOW_OK) {
|
|
|
|
g_mutex_unlock (teletext->queue_lock);
|
2010-09-13 09:59:18 +00:00
|
|
|
goto error;
|
2010-06-10 11:55:30 +00:00
|
|
|
}
|
2010-09-13 09:59:18 +00:00
|
|
|
}
|
|
|
|
g_mutex_unlock (teletext->queue_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* ERRORS */
|
|
|
|
error:
|
|
|
|
{
|
2011-05-23 13:20:45 +00:00
|
|
|
if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED
|
2012-02-08 15:37:13 +00:00
|
|
|
&& ret != GST_FLOW_FLUSHING) {
|
2010-09-13 09:59:18 +00:00
|
|
|
GST_ELEMENT_ERROR (teletext, STREAM, FAILED,
|
2011-05-23 13:20:45 +00:00
|
|
|
("Internal data stream error."), ("stream stopped, reason %s",
|
|
|
|
gst_flow_get_name (ret)));
|
2010-06-10 10:42:42 +00:00
|
|
|
return GST_FLOW_ERROR;
|
2010-09-13 09:59:18 +00:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static GstFlowReturn
|
|
|
|
gst_teletextdec_push_page (GstTeletextDec * teletext)
|
|
|
|
{
|
|
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
GstBuffer *buf;
|
|
|
|
vbi_page page;
|
|
|
|
page_info *pi;
|
|
|
|
gint pgno, subno;
|
|
|
|
gboolean success;
|
|
|
|
|
|
|
|
pi = (page_info *) g_queue_pop_head (teletext->queue);
|
|
|
|
|
|
|
|
pgno = vbi_bcd2dec (pi->pgno);
|
|
|
|
subno = vbi_bcd2dec (pi->subno);
|
|
|
|
|
|
|
|
GST_INFO_OBJECT (teletext, "Fetching teletext page %03d.%02d", pgno, subno);
|
|
|
|
|
|
|
|
success = vbi_fetch_vt_page (teletext->decoder, &page, pi->pgno, pi->subno,
|
|
|
|
VBI_WST_LEVEL_3p5, 25, FALSE);
|
|
|
|
if (G_UNLIKELY (!success))
|
|
|
|
goto fetch_page_failed;
|
|
|
|
|
|
|
|
switch (teletext->output_format) {
|
|
|
|
case GST_TELETEXTDEC_OUTPUT_FORMAT_TEXT:
|
|
|
|
ret = gst_teletextdec_export_text_page (teletext, &page, &buf);
|
|
|
|
break;
|
|
|
|
case GST_TELETEXTDEC_OUTPUT_FORMAT_HTML:
|
|
|
|
ret = gst_teletextdec_export_html_page (teletext, &page, &buf);
|
|
|
|
break;
|
|
|
|
case GST_TELETEXTDEC_OUTPUT_FORMAT_RGBA:
|
|
|
|
ret = gst_teletextdec_export_rgba_page (teletext, &page, &buf);
|
|
|
|
break;
|
2010-06-10 10:44:27 +00:00
|
|
|
case GST_TELETEXTDEC_OUTPUT_FORMAT_PANGO:
|
|
|
|
ret = gst_teletextdec_export_pango_page (teletext, &page, &buf);
|
|
|
|
break;
|
2010-09-13 09:59:18 +00:00
|
|
|
default:
|
|
|
|
g_assert_not_reached ();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
vbi_unref_page (&page);
|
|
|
|
g_free (pi);
|
|
|
|
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
|
|
goto alloc_failed;
|
|
|
|
|
|
|
|
GST_BUFFER_TIMESTAMP (buf) = teletext->in_timestamp;
|
|
|
|
GST_BUFFER_DURATION (buf) = teletext->in_duration;
|
|
|
|
|
|
|
|
GST_INFO_OBJECT (teletext, "Pushing buffer of size %d",
|
|
|
|
GST_BUFFER_SIZE (buf));
|
|
|
|
|
|
|
|
ret = gst_pad_push (teletext->srcpad, buf);
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
|
|
goto push_failed;
|
|
|
|
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
|
|
|
|
fetch_page_failed:
|
|
|
|
{
|
|
|
|
GST_ELEMENT_ERROR (teletext, RESOURCE, READ, (NULL), (NULL));
|
|
|
|
return GST_FLOW_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
alloc_failed:
|
|
|
|
{
|
|
|
|
GST_ERROR_OBJECT (teletext, "Error allocating output buffer, reason %s",
|
|
|
|
gst_flow_get_name (ret));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
push_failed:
|
|
|
|
{
|
|
|
|
GST_ERROR_OBJECT (teletext, "Pushing buffer failed, reason %s",
|
|
|
|
gst_flow_get_name (ret));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-10 10:44:27 +00:00
|
|
|
static gchar **
|
|
|
|
gst_teletextdec_vbi_page_to_text_lines (GstTeletextDec * teletext,
|
|
|
|
guint start, guint stop, vbi_page * page)
|
2010-09-13 09:59:18 +00:00
|
|
|
{
|
2010-06-10 10:44:27 +00:00
|
|
|
const guint lines_count = stop - start + 1;
|
|
|
|
const guint line_length = page->columns;
|
|
|
|
gchar **lines;
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
/* allocate a new NULL-terminated array of strings */
|
|
|
|
lines = (gchar **) g_malloc (sizeof (gchar *) * (lines_count + 1));
|
|
|
|
lines[lines_count] = g_strdup ('\0');
|
|
|
|
|
|
|
|
/* export each line in the range of the teletext page in text format */
|
|
|
|
for (i = start; i <= stop; i++) {
|
|
|
|
lines[i - start] = (gchar *) g_malloc (sizeof (gchar) * (line_length + 1));
|
|
|
|
vbi_print_page_region (page, lines[i - start], line_length + 1, "UTF-8",
|
|
|
|
TRUE, 0, 0, i, line_length, 1);
|
2010-09-13 09:59:18 +00:00
|
|
|
/* Add the null character */
|
2010-06-10 10:44:27 +00:00
|
|
|
lines[i - start][line_length] = '\0';
|
2010-09-13 09:59:18 +00:00
|
|
|
}
|
2010-06-10 10:44:27 +00:00
|
|
|
|
|
|
|
return lines;
|
2010-09-13 09:59:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static GstFlowReturn
|
|
|
|
gst_teletextdec_export_text_page (GstTeletextDec * teletext, vbi_page * page,
|
|
|
|
GstBuffer ** buf)
|
|
|
|
{
|
|
|
|
GstCaps *caps;
|
|
|
|
GstFlowReturn ret;
|
|
|
|
gchar *text;
|
|
|
|
guint size;
|
|
|
|
|
|
|
|
if (teletext->subtitles_mode) {
|
2010-06-10 10:44:27 +00:00
|
|
|
gchar **lines;
|
|
|
|
GString *subs;
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
lines = gst_teletextdec_vbi_page_to_text_lines (teletext, 1, 23, page);
|
|
|
|
subs = g_string_new ("");
|
|
|
|
/* Strip white spaces and squash blank lines */
|
|
|
|
for (i = 0; i < 23; i++) {
|
|
|
|
g_strstrip (lines[i]);
|
|
|
|
if (g_strcmp0 (lines[i], ""))
|
|
|
|
g_string_append_printf (subs, teletext->subtitles_template, lines[i]);
|
|
|
|
}
|
|
|
|
/* if the page is blank and doesn't contain any line of text, just add a
|
|
|
|
* line break */
|
|
|
|
if (!g_strcmp0 (subs->str, ""))
|
|
|
|
g_string_append (subs, "\n");
|
|
|
|
|
|
|
|
text = subs->str;
|
|
|
|
size = subs->len + 1;
|
|
|
|
g_string_free (subs, FALSE);
|
|
|
|
g_strfreev (lines);
|
2010-09-13 09:59:18 +00:00
|
|
|
} else {
|
|
|
|
size = page->columns * page->rows;
|
|
|
|
text = g_malloc (size);
|
|
|
|
vbi_print_page (page, text, size, "UTF-8", FALSE, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate new buffer */
|
|
|
|
caps = gst_caps_new_simple ("text/plain", NULL);
|
|
|
|
ret = gst_pad_alloc_buffer (teletext->srcpad, GST_BUFFER_OFFSET_NONE,
|
|
|
|
size, caps, &(*buf));
|
|
|
|
if (G_LIKELY (ret == GST_FLOW_OK))
|
|
|
|
GST_BUFFER_DATA (*buf) = GST_BUFFER_MALLOCDATA (*buf) = (guint8 *) text;
|
|
|
|
else
|
|
|
|
gst_buffer_unref (*buf);
|
|
|
|
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GstFlowReturn
|
|
|
|
gst_teletextdec_export_html_page (GstTeletextDec * teletext, vbi_page * page,
|
|
|
|
GstBuffer ** buf)
|
|
|
|
{
|
|
|
|
GstCaps *caps;
|
|
|
|
GstFlowReturn ret;
|
|
|
|
gchar *html;
|
2012-06-20 08:58:32 +00:00
|
|
|
gssize size;
|
2010-09-13 09:59:18 +00:00
|
|
|
vbi_export *ex;
|
|
|
|
gchar *err;
|
|
|
|
|
|
|
|
if (!(ex = vbi_export_new ("html", &err))) {
|
|
|
|
GST_ELEMENT_ERROR (teletext, LIBRARY, SETTINGS,
|
|
|
|
("Can't open the HTML export module: %s", err), (NULL));
|
|
|
|
g_free (err);
|
|
|
|
return GST_FLOW_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* export to NULL to get size of the memory needed to allocate the page */
|
|
|
|
size = vbi_export_mem (ex, NULL, 0, page);
|
|
|
|
if (size < 0)
|
|
|
|
return GST_FLOW_ERROR;
|
|
|
|
html = g_malloc (size);
|
|
|
|
vbi_export_mem (ex, html, size, page);
|
|
|
|
|
|
|
|
/* Allocate new buffer */
|
|
|
|
caps = gst_caps_new_simple ("text/html", NULL);
|
|
|
|
ret = gst_pad_alloc_buffer (teletext->srcpad, GST_BUFFER_OFFSET_NONE,
|
|
|
|
size, caps, &(*buf));
|
|
|
|
if (G_LIKELY (ret == GST_FLOW_OK))
|
|
|
|
GST_BUFFER_DATA (*buf) = GST_BUFFER_MALLOCDATA (*buf) = (guint8 *) html;
|
|
|
|
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GstFlowReturn
|
|
|
|
gst_teletextdec_export_rgba_page (GstTeletextDec * teletext, vbi_page * page,
|
|
|
|
GstBuffer ** buf)
|
|
|
|
{
|
|
|
|
guint size;
|
|
|
|
GstCaps *caps, *out_caps;
|
|
|
|
GstFlowReturn ret;
|
|
|
|
gint width, height;
|
|
|
|
GstPadTemplate *templ;
|
|
|
|
|
|
|
|
/* one character occupies 12 x 10 pixels */
|
|
|
|
width = page->columns * 12;
|
|
|
|
height = page->rows * 10;
|
|
|
|
|
|
|
|
caps = gst_caps_new_simple ("video/x-raw-rgb",
|
|
|
|
"width", G_TYPE_INT, width,
|
|
|
|
"height", G_TYPE_INT, height,
|
|
|
|
"framerate", GST_TYPE_FRACTION, teletext->rate_numerator,
|
|
|
|
teletext->rate_denominator, NULL);
|
|
|
|
|
|
|
|
templ = gst_static_pad_template_get (&src_template);
|
|
|
|
out_caps = gst_caps_intersect (caps, gst_pad_template_get_caps (templ));
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
gst_object_unref (templ);
|
|
|
|
|
|
|
|
size = (guint) width *(guint) height *sizeof (vbi_rgba);
|
|
|
|
|
|
|
|
ret = gst_pad_alloc_buffer_and_set_caps (teletext->srcpad,
|
|
|
|
GST_BUFFER_OFFSET_NONE, size, out_caps, &(*buf));
|
|
|
|
|
|
|
|
if (ret == GST_FLOW_OK) {
|
|
|
|
GST_DEBUG_OBJECT (teletext, "Creating image with %d rows and %d cols",
|
|
|
|
page->rows, page->columns);
|
|
|
|
vbi_draw_vt_page (page, VBI_PIXFMT_RGBA32_LE,
|
|
|
|
(vbi_rgba *) GST_BUFFER_DATA (*buf), FALSE, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
gst_caps_unref (out_caps);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-06-10 10:44:27 +00:00
|
|
|
static GstFlowReturn
|
|
|
|
gst_teletextdec_export_pango_page (GstTeletextDec * teletext, vbi_page * page,
|
|
|
|
GstBuffer ** buf)
|
|
|
|
{
|
|
|
|
vbi_char *acp;
|
2010-09-10 12:34:42 +00:00
|
|
|
const guint rows = page->rows;
|
|
|
|
gchar **colors;
|
|
|
|
gchar **lines;
|
|
|
|
GString *subs;
|
|
|
|
GstCaps *caps;
|
|
|
|
GstFlowReturn ret;
|
|
|
|
guint start, stop;
|
|
|
|
guint i, j;
|
|
|
|
|
|
|
|
colors = (gchar **) g_malloc (sizeof (gchar *) * (rows + 1));
|
|
|
|
colors[rows] = g_strdup ('\0');
|
2010-06-10 10:44:27 +00:00
|
|
|
|
2010-09-10 12:34:42 +00:00
|
|
|
/* parse all the lines and approximate it's foreground color using the first
|
2010-06-10 10:44:27 +00:00
|
|
|
* non null character */
|
|
|
|
for (acp = page->text, i = 0; i < page->rows; acp += page->columns, i++) {
|
|
|
|
for (j = 0; j < page->columns; j++) {
|
2010-09-10 12:34:42 +00:00
|
|
|
colors[i] = g_strdup (default_color_map[7]);
|
|
|
|
if (acp[j].unicode != 0x20) {
|
|
|
|
colors[i] = g_strdup (default_color_map[acp[j].foreground]);
|
|
|
|
break;
|
2010-06-10 10:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-10 12:34:42 +00:00
|
|
|
/* get an array of strings with each line of the telext page */
|
|
|
|
start = teletext->subtitles_mode ? 1 : 0;
|
|
|
|
stop = teletext->subtitles_mode ? rows - 2 : rows - 1;
|
|
|
|
lines = gst_teletextdec_vbi_page_to_text_lines (teletext, start, stop, page);
|
2010-06-10 10:44:27 +00:00
|
|
|
|
2010-09-10 12:34:42 +00:00
|
|
|
/* format each line in pango markup */
|
|
|
|
subs = g_string_new ("");
|
|
|
|
for (i = start; i <= stop; i++) {
|
|
|
|
g_string_append_printf (subs, PANGO_TEMPLATE,
|
|
|
|
teletext->font_description, colors[i], lines[i - start]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate new buffer */
|
|
|
|
caps = gst_caps_new_simple ("text/x-pango-markup", NULL);
|
|
|
|
ret = gst_pad_alloc_buffer (teletext->srcpad, GST_BUFFER_OFFSET_NONE,
|
|
|
|
subs->len + 1, caps, &(*buf));
|
|
|
|
if (G_LIKELY (ret == GST_FLOW_OK))
|
|
|
|
GST_BUFFER_DATA (*buf) = GST_BUFFER_MALLOCDATA (*buf) =
|
|
|
|
(guint8 *) subs->str;
|
|
|
|
else
|
|
|
|
gst_buffer_unref (*buf);
|
|
|
|
|
|
|
|
g_strfreev (lines);
|
|
|
|
g_strfreev (colors);
|
|
|
|
g_string_free (subs, FALSE);
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
return ret;
|
2010-06-10 10:44:27 +00:00
|
|
|
}
|
|
|
|
|
2010-09-13 09:59:18 +00:00
|
|
|
static gboolean
|
|
|
|
gst_teletextdec_push_preroll_buffer (GstTeletextDec * teletext)
|
|
|
|
{
|
|
|
|
GstFlowReturn ret;
|
|
|
|
GstBuffer *buf;
|
|
|
|
gboolean res = TRUE;
|
|
|
|
GstStructure *structure;
|
|
|
|
const gchar *mimetype;
|
|
|
|
GstCaps *out_caps, *peer_caps, *pad_caps;
|
|
|
|
|
|
|
|
/* the stream is sparse, we send a dummy buffer for preroll */
|
|
|
|
peer_caps = gst_pad_peer_get_caps (teletext->srcpad);
|
|
|
|
pad_caps = gst_pad_get_caps (teletext->srcpad);
|
|
|
|
out_caps = gst_caps_intersect (pad_caps, peer_caps);
|
|
|
|
|
|
|
|
if (gst_caps_is_empty (out_caps)) {
|
|
|
|
res = FALSE;
|
|
|
|
goto beach;
|
|
|
|
}
|
|
|
|
|
2011-05-23 13:32:09 +00:00
|
|
|
gst_caps_truncate (out_caps);
|
2010-09-13 09:59:18 +00:00
|
|
|
structure = gst_caps_get_structure (out_caps, 0);
|
|
|
|
mimetype = gst_structure_get_name (structure);
|
|
|
|
if (g_strcmp0 (mimetype, "video/x-raw-rgb") == 0) {
|
|
|
|
/* omit preroll buffer for this format */
|
|
|
|
goto beach;
|
|
|
|
}
|
|
|
|
|
2011-05-23 13:20:45 +00:00
|
|
|
buf = gst_buffer_new_and_alloc (1);
|
2011-05-23 13:14:04 +00:00
|
|
|
GST_BUFFER_DATA (buf)[0] = 0;
|
2010-09-13 09:59:18 +00:00
|
|
|
gst_buffer_set_caps (buf, out_caps);
|
|
|
|
ret = gst_pad_push (teletext->srcpad, buf);
|
|
|
|
if (ret != GST_FLOW_OK)
|
|
|
|
res = FALSE;
|
|
|
|
|
|
|
|
beach:
|
|
|
|
{
|
|
|
|
gst_caps_unref (out_caps);
|
|
|
|
gst_caps_unref (pad_caps);
|
|
|
|
gst_caps_unref (peer_caps);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Converts the line_offset / field_parity byte of a VBI data unit. */
|
|
|
|
static void
|
|
|
|
gst_teletextdec_lofp_to_line (guint * field, guint * field_line,
|
|
|
|
guint * frame_line, guint lofp, systems system)
|
|
|
|
{
|
2011-05-23 13:32:09 +00:00
|
|
|
guint line_offset;
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
/* field_parity */
|
|
|
|
*field = !(lofp & (1 << 5));
|
|
|
|
|
|
|
|
line_offset = lofp & 31;
|
|
|
|
|
|
|
|
if (line_offset > 0) {
|
|
|
|
static const guint field_start[2][2] = {
|
|
|
|
{0, 263},
|
|
|
|
{0, 313},
|
|
|
|
};
|
|
|
|
|
|
|
|
*field_line = line_offset;
|
|
|
|
*frame_line = field_start[system][*field] + line_offset;
|
|
|
|
} else {
|
|
|
|
*field_line = 0;
|
|
|
|
*frame_line = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
gst_teletextdec_line_address (GstTeletextDec * teletext,
|
|
|
|
GstTeletextFrame * frame, vbi_sliced ** spp, guint lofp, systems system)
|
|
|
|
{
|
|
|
|
guint field;
|
|
|
|
guint field_line;
|
|
|
|
guint frame_line;
|
|
|
|
|
|
|
|
if (G_UNLIKELY (frame->current_slice >= frame->sliced_end)) {
|
|
|
|
GST_LOG_OBJECT (teletext, "Out of sliced VBI buffer space (%d lines).",
|
|
|
|
(int) (frame->sliced_end - frame->sliced_begin));
|
|
|
|
return VBI_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
gst_teletextdec_lofp_to_line (&field, &field_line, &frame_line, lofp, system);
|
|
|
|
|
|
|
|
GST_LOG_OBJECT (teletext, "Line %u/%u=%u.", field, field_line, frame_line);
|
|
|
|
|
|
|
|
if (frame_line != 0) {
|
|
|
|
GST_LOG_OBJECT (teletext, "Last frame Line %u.", frame->last_frame_line);
|
|
|
|
if (frame_line <= frame->last_frame_line) {
|
|
|
|
GST_LOG_OBJECT (teletext, "New frame");
|
|
|
|
return VBI_NEW_FRAME;
|
|
|
|
}
|
|
|
|
|
2012-06-20 08:59:08 +00:00
|
|
|
/* FIXME : This never happens, since lofp is a guint8 */
|
|
|
|
#if 0
|
2010-09-13 09:59:18 +00:00
|
|
|
/* new segment flag */
|
|
|
|
if (lofp < 0) {
|
|
|
|
GST_LOG_OBJECT (teletext, "New frame");
|
|
|
|
return VBI_NEW_FRAME;
|
|
|
|
}
|
2012-06-20 08:59:08 +00:00
|
|
|
#endif
|
2010-09-13 09:59:18 +00:00
|
|
|
|
|
|
|
frame->last_field = field;
|
|
|
|
frame->last_field_line = field_line;
|
|
|
|
frame->last_frame_line = frame_line;
|
|
|
|
|
|
|
|
*spp = frame->current_slice++;
|
|
|
|
(*spp)->line = frame_line;
|
|
|
|
} else {
|
|
|
|
/* Undefined line. */
|
|
|
|
return VBI_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return VBI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_teletextdec_extract_data_units (GstTeletextDec * teletext,
|
|
|
|
GstTeletextFrame * f, guint8 * packet, guint * offset, gint size)
|
|
|
|
{
|
|
|
|
guint8 *data_unit;
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
while (*offset < size) {
|
|
|
|
vbi_sliced *s = NULL;
|
|
|
|
gint data_unit_id, data_unit_length;
|
|
|
|
|
|
|
|
data_unit = packet + *offset;
|
|
|
|
data_unit_id = data_unit[0];
|
|
|
|
data_unit_length = data_unit[1];
|
|
|
|
GST_LOG_OBJECT (teletext, "vbi header %02x %02x %02x\n", data_unit[0],
|
|
|
|
data_unit[1], data_unit[2]);
|
|
|
|
|
|
|
|
switch (data_unit_id) {
|
|
|
|
case DATA_UNIT_STUFFING:
|
|
|
|
{
|
|
|
|
*offset += 2 + data_unit_length;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case DATA_UNIT_EBU_TELETEXT_NON_SUBTITLE:
|
|
|
|
case DATA_UNIT_EBU_TELETEXT_SUBTITLE:
|
|
|
|
{
|
|
|
|
gint res;
|
|
|
|
|
|
|
|
if (G_UNLIKELY (data_unit_length != 1 + 1 + 42)) {
|
|
|
|
/* Skip this data unit */
|
|
|
|
GST_WARNING_OBJECT (teletext, "The data unit length is not 44 bytes");
|
|
|
|
*offset += 2 + data_unit_length;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
res =
|
|
|
|
gst_teletextdec_line_address (teletext, f, &s, data_unit[2],
|
|
|
|
SYSTEM_625);
|
|
|
|
if (G_UNLIKELY (res == VBI_ERROR)) {
|
|
|
|
/* Can't retrieve line address, skip this data unit */
|
|
|
|
GST_WARNING_OBJECT (teletext,
|
|
|
|
"Could not retrieve line address for this data unit");
|
|
|
|
return VBI_ERROR;
|
|
|
|
}
|
|
|
|
if (G_UNLIKELY (f->last_field_line > 0
|
|
|
|
&& (f->last_field_line - 7 >= 23 - 7))) {
|
|
|
|
GST_WARNING_OBJECT (teletext, "Bad line: %d", f->last_field_line - 7);
|
|
|
|
return VBI_ERROR;
|
|
|
|
}
|
|
|
|
if (res == VBI_NEW_FRAME) {
|
|
|
|
/* New frame */
|
|
|
|
return VBI_NEW_FRAME;
|
|
|
|
}
|
|
|
|
s->id = VBI_SLICED_TELETEXT_B;
|
|
|
|
for (i = 0; i < 42; i++)
|
|
|
|
s->data[i] = vbi_rev8 (data_unit[4 + i]);
|
|
|
|
*offset += 46;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case DATA_UNIT_ZVBI_WSS_CPR1204:
|
|
|
|
case DATA_UNIT_ZVBI_CLOSED_CAPTION_525:
|
|
|
|
case DATA_UNIT_ZVBI_MONOCHROME_SAMPLES_525:
|
|
|
|
case DATA_UNIT_VPS:
|
|
|
|
case DATA_UNIT_WSS:
|
|
|
|
case DATA_UNIT_CLOSED_CAPTION:
|
|
|
|
case DATA_UNIT_MONOCHROME_SAMPLES:
|
|
|
|
{
|
|
|
|
/*Not supported yet */
|
|
|
|
*offset += 2 + data_unit_length;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
/* corrupted stream, increase the offset by one until we sync */
|
|
|
|
GST_LOG_OBJECT (teletext, "Corrupted, increasing offset by one");
|
|
|
|
*offset += 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return VBI_SUCCESS;
|
|
|
|
}
|