From 8bb6ff414f320b84a04abeaff25c5d261d087ace Mon Sep 17 00:00:00 2001 From: Andoni Morales Alastruey Date: Mon, 13 Sep 2010 11:59:18 +0200 Subject: [PATCH] teletextdec: New teletext decoder plugin based in zvbi https://bugzilla.gnome.org/show_bug.cgi?id=619739 --- configure.ac | 8 + ext/Makefile.am | 8 + ext/teletextdec/Makefile.am | 10 + ext/teletextdec/gstteletextdec.c | 1114 ++++++++++++++++++++++++++++++ ext/teletextdec/gstteletextdec.h | 106 +++ ext/teletextdec/teletext.c | 43 ++ 6 files changed, 1289 insertions(+) create mode 100644 ext/teletextdec/Makefile.am create mode 100644 ext/teletextdec/gstteletextdec.c create mode 100644 ext/teletextdec/gstteletextdec.h create mode 100644 ext/teletextdec/teletext.c diff --git a/configure.ac b/configure.ac index 617b8b13c0..d97422167a 100644 --- a/configure.ac +++ b/configure.ac @@ -1382,6 +1382,12 @@ AG_GST_CHECK_FEATURE(TIMIDITY, [timidity midi soft synth plugin], timidity, [ AC_SUBST(TIMIDITY_LIBS) ]) +dnl *** teletextdec *** +translit(dnm, m, l) AM_CONDITIONAL(USE_TELETEXTDEC, true) +AG_GST_CHECK_FEATURE(TELETEXTDEC, [Teletext decoder], teletextdec, [ + AG_GST_PKG_CHECK_MODULES(TELETEXTDEC, zvbi-0.2) + ]) + dnl *** wildmidi *** translit(dnm, m, l) AM_CONDITIONAL(USE_WILDMIDI, true) AG_GST_CHECK_FEATURE(WILDMIDI, [wildmidi midi soft synth plugin], wildmidi, [ @@ -1719,6 +1725,7 @@ AM_CONDITIONAL(USE_SCHRO, false) AM_CONDITIONAL(USE_ZBAR, false) AM_CONDITIONAL(USE_VP8, false) AM_CONDITIONAL(USE_RTMP, false) +AM_CONDITIONAL(USE_TELETEXTDEC, false) fi dnl of EXT plugins @@ -1947,6 +1954,7 @@ ext/schroedinger/Makefile ext/sdl/Makefile ext/sndfile/Makefile ext/soundtouch/Makefile +ext/teletextdec/Makefile ext/gme/Makefile ext/gsettings/Makefile ext/gsettings/org.freedesktop.gstreamer.default-elements.gschema.xml diff --git a/ext/Makefile.am b/ext/Makefile.am index 9ece17a38f..70d4c69c31 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -336,6 +336,12 @@ else SWFDEC_DIR= endif +if USE_TELETEXTDEC +TELETEXT_DIR=teletextdec +else +TELETEXT_DIR= +endif + if USE_VP8 VP8_DIR=vp8 else @@ -423,6 +429,7 @@ SUBDIRS=\ $(GME_DIR) \ $(SPC_DIR) \ $(SWFDEC_DIR) \ + $(TELETEXTDEC_DIR) \ $(TIMIDITY_DIR) \ $(VP8_DIR) \ $(XVID_DIR) \ @@ -473,6 +480,7 @@ DIST_SUBDIRS = \ spc \ gme \ swfdec \ + teletextdec \ timidity \ voaacenc \ voamrwbenc \ diff --git a/ext/teletextdec/Makefile.am b/ext/teletextdec/Makefile.am new file mode 100644 index 0000000000..c153c563b2 --- /dev/null +++ b/ext/teletextdec/Makefile.am @@ -0,0 +1,10 @@ +plugin_LTLIBRARIES = libgstteletextdec.la + +libgstteletextdec_la_SOURCES = gstteletextdec.c teletext.c + +libgstteletextdec_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(TELETEXTDEC_CFLAGS) +libgstteletextdec_la_LIBADD = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_LIBS) $(TELETEXTDEC_LIBS) +libgstteletextdec_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstteletextdec_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstteletextdec.h diff --git a/ext/teletextdec/gstteletextdec.c b/ext/teletextdec/gstteletextdec.c new file mode 100644 index 0000000000..74295c6a7e --- /dev/null +++ b/ext/teletextdec/gstteletextdec.c @@ -0,0 +1,1114 @@ +/* + * GStreamer + * Copyright (C) 2009 Sebastian + * Copyright (C) 2010 Andoni Morales Alastruey + * + * 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 + * + * + * Example launch line + * |[ + * gst-launch -v -m filesrc location=recording.mpeg ! mpegtsdemux ! private/teletext ! teletextdec ! ffmpegcolorspace ! ximagesink + * ]| + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#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 + +/* Filter signals and args */ +enum +{ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_PAGENO, + PROP_SUBNO, + PROP_SUBTITLES_MODE, + PROP_SUBS_TEMPLATE +}; + +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; + +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, + GST_STATIC_CAPS (GST_VIDEO_CAPS_RGBA "; text/plain ; text/html") + ); + +/* 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); + +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 , " + "Andoni Morales Alastruey "); + + 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); + gstelement_class->change_state = gst_teletextdec_change_state; + + g_object_class_install_property (gobject_class, PROP_PAGENO, + g_param_spec_int ("page", "Page number", + "Number of page that should displayed", + 100, 999, 100, G_PARAM_READWRITE)); + + 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)", + -1, 0x99, -1, G_PARAM_READWRITE)); + + 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 " + "the teletext state lines", FALSE, G_PARAM_READWRITE)); + + 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", + g_strescape ("%s\n", NULL), G_PARAM_READWRITE)); +} + +/* 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); + + 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); + + 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; + 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; + 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; + + 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; + } + + gst_pad_set_caps (gst_pad_get_peer (pad), caps); + + 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"); + } 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); + if (ret != GST_FLOW_OK) + goto error; + } + g_mutex_unlock (teletext->queue_lock); + + return ret; + +/* ERRORS */ +error: + { + if (GST_FLOW_IS_FATAL (ret)) { + GST_ELEMENT_ERROR (teletext, STREAM, FAILED, + ("Internal data stream error."), + ("stream stopped, reason %s", gst_flow_get_name (ret))); + gst_pad_push_event (teletext->srcpad, gst_event_new_eos ()); + } + 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; + 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; + } +} + +static gint +gst_teletextdec_parse_subtitles_page (GstTeletextDec * teletext, + vbi_page * page, gchar ** text) +{ + const gint line_length = page->columns; + gchar *line; + GString *subs; + gint length, i; + + subs = g_string_new (""); + line = g_malloc (line_length + 1); + /* Print lines 2 to 23 */ + for (i = 1; i < 23; i++) { + vbi_print_page_region (page, line, line_length + 1, "UTF-8", TRUE, 0, + 0, i, page->columns, 1); + /* Add the null character */ + line[line_length] = '\0'; + /* Strip blank lines */ + g_strstrip (line); + if (g_strcmp0 (line, "")) { + g_string_append_printf (subs, teletext->subtitles_template, line); + } + } + if (!g_strcmp0 (subs->str, "")) + g_string_append (subs, "\n"); + + *text = subs->str; + length = subs->len + 1; + g_string_free (subs, FALSE); + g_free (line); + return length; +} + +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) { + size = gst_teletextdec_parse_subtitles_page (teletext, page, &text); + } 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; + guint size; + 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; +} + +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; + } + + 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; + } + + buf = gst_buffer_new (); + 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) +{ + uint line_offset; + + /* 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; + } + + /* new segment flag */ + if (lofp < 0) { + GST_LOG_OBJECT (teletext, "New frame"); + return VBI_NEW_FRAME; + } + + 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; +} diff --git a/ext/teletextdec/gstteletextdec.h b/ext/teletextdec/gstteletextdec.h new file mode 100644 index 0000000000..807c0034f4 --- /dev/null +++ b/ext/teletextdec/gstteletextdec.h @@ -0,0 +1,106 @@ +/* + * GStreamer + * Copyright (C) 2009 Sebastian + * Copyright (C) 2010 Andoni Morales Alastruey + * + * This library is free software; you can redistribute it and/or + * mod1ify 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 + */ + +#ifndef __GST_TELETEXTDEC_H__ +#define __GST_TELETEXTDEC_H__ + +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_TELETEXTDEC \ + (gst_teletextdec_get_type()) +#define GST_TELETEXTDEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TELETEXTDEC,GstTeletextDec)) +#define GST_TELETEXTDEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TELETEXTDEC,GstTeletextDecClass)) +#define GST_IS_TELETEXTDEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TELETEXTDEC)) +#define GST_IS_TELETEXTDEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TELETEXTDEC)) +typedef struct _GstTeletextDec GstTeletextDec; +typedef struct _GstTeletextDecClass GstTeletextDecClass; +typedef struct _GstTeletextFrame GstTeletextFrame; +typedef enum _GstTeletextOutputFormat GstTeletextOutputFormat; + +enum _GstTeletextOutputFormat +{ + GST_TELETEXTDEC_OUTPUT_FORMAT_RGBA, + GST_TELETEXTDEC_OUTPUT_FORMAT_TEXT, + GST_TELETEXTDEC_OUTPUT_FORMAT_HTML, + GST_TELETEXTDEC_OUTPUT_FORMAT_SUBTITLES +}; + +typedef void (*GstTeletextProcessBufferFunc) (GstTeletextDec * + teletext, GstBuffer * buf); + +struct _GstTeletextDec +{ + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + + GstClockTime in_timestamp; + GstClockTime in_duration; + gint rate_numerator; + gint rate_denominator; + + /* Props */ + gint pageno; + gint subno; + gboolean subtitles_mode; + gchar *subtitles_template; + + vbi_dvb_demux *demux; + vbi_decoder *decoder; + vbi_export *exporter; + GQueue *queue; + GMutex *queue_lock; + + GstTeletextFrame *frame; + float last_ts; + GstTeletextOutputFormat output_format; + + GstTeletextProcessBufferFunc process_buf_func; +}; + +struct _GstTeletextFrame +{ + vbi_sliced *sliced_begin; + vbi_sliced *sliced_end; + vbi_sliced *current_slice; + + guint last_field; + guint last_field_line; + guint last_frame_line; +}; + + +struct _GstTeletextDecClass +{ + GstElementClass parent_class; +}; + +GType gst_teletextdec_get_type (void); + +G_END_DECLS +#endif /* __GST_TELETEXTDEC_H__ */ diff --git a/ext/teletextdec/teletext.c b/ext/teletextdec/teletext.c new file mode 100644 index 0000000000..f4661745cf --- /dev/null +++ b/ext/teletextdec/teletext.c @@ -0,0 +1,43 @@ +/* + * GStreamer + * Copyright (C) 2009 Sebastian + * + * This library is free software; you can redistribute it and/or + * mod1ify 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstteletextdec.h" + +/* entry point to initialize the plug-in + * initialize the plug-in itself + * register the element factories and other features + */ +static gboolean +teletext_init (GstPlugin * teletext) +{ + return gst_element_register (teletext, "teletextdec", GST_RANK_NONE, + GST_TYPE_TELETEXTDEC); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "teletext", + "Teletext plugin", + teletext_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/")