From e88984ccbd4b2b7b5527cbc3ff488c5c85bb1e97 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Sun, 15 Feb 2009 18:35:04 +0000 Subject: [PATCH] add new Kate plugin, for Kate overlay streams katedec: Kate decoder (text only) kateenc: Kate encoder (text and DVD SPU only) katetag: Kate tagger kateparse: Kate parser tiger: Kate renderer using the Tiger rendering library Fixes #525743. --- LICENSE_readme | 3 + REQUIREMENTS | 6 +- configure.ac | 26 + docs/plugins/Makefile.am | 5 + .../plugins/gst-plugins-bad-plugins-docs.sgml | 6 + .../gst-plugins-bad-plugins-sections.txt | 70 + ext/Makefile.am | 8 + ext/kate/Makefile.am | 20 + ext/kate/README | 20 + ext/kate/gstkate.c | 150 ++ ext/kate/gstkate.h | 55 + ext/kate/gstkatedec.c | 318 +++ ext/kate/gstkatedec.h | 88 + ext/kate/gstkateenc.c | 1834 +++++++++++++++++ ext/kate/gstkateenc.h | 120 ++ ext/kate/gstkateparse.c | 613 ++++++ ext/kate/gstkateparse.h | 78 + ext/kate/gstkatetag.c | 347 ++++ ext/kate/gstkatetag.h | 65 + ext/kate/gstkatetiger.c | 802 +++++++ ext/kate/gstkatetiger.h | 108 + ext/kate/gstkateutil.c | 371 ++++ ext/kate/gstkateutil.h | 77 + gst-plugins-bad.spec.in | 1 + tests/check/Makefile.am | 11 + tests/check/elements/kate.c | 839 ++++++++ 26 files changed, 6039 insertions(+), 2 deletions(-) create mode 100644 ext/kate/Makefile.am create mode 100644 ext/kate/README create mode 100644 ext/kate/gstkate.c create mode 100644 ext/kate/gstkate.h create mode 100644 ext/kate/gstkatedec.c create mode 100644 ext/kate/gstkatedec.h create mode 100644 ext/kate/gstkateenc.c create mode 100644 ext/kate/gstkateenc.h create mode 100644 ext/kate/gstkateparse.c create mode 100644 ext/kate/gstkateparse.h create mode 100644 ext/kate/gstkatetag.c create mode 100644 ext/kate/gstkatetag.h create mode 100644 ext/kate/gstkatetiger.c create mode 100644 ext/kate/gstkatetiger.h create mode 100644 ext/kate/gstkateutil.c create mode 100644 ext/kate/gstkateutil.h create mode 100644 tests/check/elements/kate.c diff --git a/LICENSE_readme b/LICENSE_readme index 67fd312c3c..718d6d23bd 100644 --- a/LICENSE_readme +++ b/LICENSE_readme @@ -85,6 +85,8 @@ textoverlay pango (http://www.pango.org/) dirac (http://www.bbc.co.uk/rd/projects/dirac/) effectv (Our ports was relicensed)(http://effectv.sourceforge.net/) musepack (http://www.musepack.net/) +kate libtiger (http://libtiger.googlecode.com/) + Optional, only for the tiger element Plugins which use a BSD covered library are as follows: @@ -93,6 +95,7 @@ vorbis libogg/libvorbis (http://www.xiph.org/ogg/vorbis/) gsttheora libtheora (http://www.theora.org/) speex (http://www.speex.org/) flac libFLAC (http://flac.sourceforge.net/) +kate libkate (http://libkate.googlecode.com/) Plugins based on libraries with other free licenses: diff --git a/REQUIREMENTS b/REQUIREMENTS index 121f73c26b..773996f7a9 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -118,9 +118,11 @@ libtheora (for Ogg Theora video support) libmms (for MMS protocol support) (http://www.sf.net/projects/libmms) libamrnb (for AMR-NB support) - (http://http://www.penguin.cz/~utx/amr) + (http://www.penguin.cz/~utx/amr) libamrwb (for AMR-WB support) - (http://http://www.penguin.cz/~utx/amr) + (http://www.penguin.cz/~utx/amr) +libkate (for Kate support) + (http://libkate.googlecode.com/) Optional (debian) packages: diff --git a/configure.ac b/configure.ac index fa52dde57e..5834cbd8b6 100644 --- a/configure.ac +++ b/configure.ac @@ -944,6 +944,30 @@ AG_GST_CHECK_FEATURE(JP2K, [jp2k], jp2kdec jp2kenc, [ AC_SUBST(JP2K_LIBS) ]) +dnl *** kate *** +translit(dnm, m, l) AM_CONDITIONAL(USE_KATE, true) +AG_GST_CHECK_FEATURE(KATE, [Kate], kate, [ + PKG_CHECK_MODULES(KATE, kate >= 0.1.7, HAVE_KATE="yes", [ + HAVE_KATE="no" + AC_MSG_RESULT(no) + ]) + AC_SUBST(KATE_CFLAGS) + AC_SUBST(KATE_LIBS) + PKG_CHECK_MODULES(TIGER, tiger >= 0.3.2, + [ + HAVE_TIGER="yes" + AC_DEFINE(HAVE_TIGER, 1, [Define if libtiger is available]) + ], + [ + HAVE_TIGER="no" + AC_MSG_RESULT(no) + ] + ) + AM_CONDITIONAL(USE_TIGER, test "x$HAVE_TIGER" = "xyes") + AC_SUBST(TIGER_CFLAGS) + AC_SUBST(TIGER_LIBS) +]) + dnl *** ladspa *** translit(dnm, m, l) AM_CONDITIONAL(USE_LADSPA, true) AG_GST_CHECK_FEATURE(LADSPA, [ladspa], ladspa, [ @@ -1526,6 +1550,7 @@ AM_CONDITIONAL(USE_THEORADEC, false) AM_CONDITIONAL(USE_TIMIDITY, false) AM_CONDITIONAL(USE_XVID, false) AM_CONDITIONAL(USE_WILDMIDI, false) +AM_CONDITIONAL(USE_KATE, false) AM_CONDITIONAL(USE_WININET, false) AM_CONDITIONAL(USE_ACM, false) AM_CONDITIONAL(USE_VDPAU, false) @@ -1698,6 +1723,7 @@ ext/gsm/Makefile ext/ivorbis/Makefile ext/jack/Makefile ext/jp2k/Makefile +ext/kate/Makefile ext/ladspa/Makefile ext/lv2/Makefile ext/libmms/Makefile diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am index 2bda68f255..98598b1702 100644 --- a/docs/plugins/Makefile.am +++ b/docs/plugins/Makefile.am @@ -100,6 +100,11 @@ EXTRA_HFILES = \ $(top_srcdir)/ext/ivorbis/vorbisdec.h \ $(top_srcdir)/ext/jack/gstjackaudiosrc.h \ $(top_srcdir)/ext/jack/gstjackaudiosink.h \ + $(top_srcdir)/ext/kate/gstkateenc.h \ + $(top_srcdir)/ext/kate/gstkatedec.h \ + $(top_srcdir)/ext/kate/gstkateparse.h \ + $(top_srcdir)/ext/kate/gstkatetag.h \ + $(top_srcdir)/ext/kate/gstkatetiger.h \ $(top_srcdir)/ext/musicbrainz/gsttrm.h \ $(top_srcdir)/ext/metadata/gstbasemetadata.h \ $(top_srcdir)/ext/metadata/gstmetadatademux.h \ diff --git a/docs/plugins/gst-plugins-bad-plugins-docs.sgml b/docs/plugins/gst-plugins-bad-plugins-docs.sgml index 21ac610136..8d1dba415d 100644 --- a/docs/plugins/gst-plugins-bad-plugins-docs.sgml +++ b/docs/plugins/gst-plugins-bad-plugins-docs.sgml @@ -50,6 +50,10 @@ + + + + @@ -76,6 +80,7 @@ + @@ -120,6 +125,7 @@ + diff --git a/docs/plugins/gst-plugins-bad-plugins-sections.txt b/docs/plugins/gst-plugins-bad-plugins-sections.txt index e5742a8547..c220f0ca47 100644 --- a/docs/plugins/gst-plugins-bad-plugins-sections.txt +++ b/docs/plugins/gst-plugins-bad-plugins-sections.txt @@ -471,6 +471,76 @@ GstLiveAdderFunction gst_live_adder_get_type +
+element-katedec +katedec +GstKateDec + +GstKateDecClass +GST_KATE_DEC +GST_KATE_DEC_CLASS +GST_IS_KATE_DEC +GST_IS_KATE_DEC_CLASS +GST_TYPE_KATE_DEC +gst_kate_dec_get_type +
+ +
+element-kateenc +kateenc +GstKateEnc + +GstKateEncClass +GST_KATE_ENC +GST_KATE_ENC_CLASS +GST_IS_KATE_ENC +GST_IS_KATE_ENC_CLASS +GST_TYPE_KATE_ENC +gst_kate_enc_get_type +
+ +
+element-kateparse +kateparse +GstKateParse + +GstKateParseClass +GST_KATE_PARSE +GST_KATE_PARSE_CLASS +GST_IS_KATE_PARSE +GST_IS_KATE_PARSE_CLASS +GST_TYPE_KATE_PARSE +gst_kate_parse_get_type +
+ +
+element-katetag +katetag +GstKateTag + +GstKateTagClass +GST_KATE_TAG +GST_KATE_TAG_CLASS +GST_IS_KATE_TAG +GST_IS_KATE_TAG_CLASS +GST_TYPE_KATE_TAG +gst_kate_tag_get_type +
+ +
+element-tiger +tiger +GstKateTiger + +GstKateTigerClass +GST_KATE_TIGER +GST_KATE_TIGER_CLASS +GST_IS_KATE_TIGER +GST_IS_KATE_TIGER_CLASS +GST_TYPE_KATE_TIGER +gst_kate_tiger_get_type +
+
gstbasemetadata GstBaseMetadata diff --git a/ext/Makefile.am b/ext/Makefile.am index a2ecd18d63..fafa11469e 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -148,6 +148,12 @@ else JP2K_DIR = endif +if USE_KATE +KATE_DIR=kate +else +KATE_DIR= +endif + if USE_LADSPA LADSPA_DIR = ladspa else @@ -362,6 +368,7 @@ SUBDIRS=\ $(IVORBIS_DIR) \ $(JACK_DIR) \ $(JP2K_DIR) \ + $(KATE_DIR) \ $(LADSPA_DIR) \ $(LV2_DIR) \ $(LCS_DIR) \ @@ -411,6 +418,7 @@ DIST_SUBDIRS = \ ladspa \ jack \ jp2k \ + kate \ libmms \ lv2 \ dts \ diff --git a/ext/kate/Makefile.am b/ext/kate/Makefile.am new file mode 100644 index 0000000000..f907da5f69 --- /dev/null +++ b/ext/kate/Makefile.am @@ -0,0 +1,20 @@ +# plugindir is set in configure + +plugin_LTLIBRARIES = libgstkate.la + +# sources used to compile this plug-in +libgstkate_la_SOURCES = gstkate.c gstkatedec.c gstkateenc.c gstkateparse.c gstkatetag.c gstkateutil.c +if USE_TIGER +libgstkate_la_SOURCES += gstkatetiger.c +endif + +# flags used to compile this plugin +libgstkate_la_CFLAGS = $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(TIGER_CFLAGS) $(KATE_CFLAGS) +libgstkate_la_LIBADD = $(GST_PLUGIN_BASE_LIBS) -lgsttag-$(GST_MAJORMINOR) $(GST_LIBS) $(TIGER_LIBS) $(KATE_LIBS) +libgstkate_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstkate_la_LIBTOOLFLAGS = --tag=disable-static + +# headers we need but don't want installed +noinst_HEADERS = gstkate.h gstkatedec.h gstkateenc.h gstkateparse.h gstkatetag.h gstkateutil.h gstkatetiger.h + +EXTRA_DIST=README diff --git a/ext/kate/README b/ext/kate/README new file mode 100644 index 0000000000..394b057ae2 --- /dev/null +++ b/ext/kate/README @@ -0,0 +1,20 @@ +Kate is a free codec for text based data, which can also carry images. +It is typically used to create overlays on a video. + +libkate, a codec for Kate streams, is required to build this plugin: +http://libkate.googlecode.com/ + +libtiger, a rendering library for Kate streams, is optional: +http://libtiger.googlecode.com/ + +The Kate plugin contains various elements to manipulate Kate streams: + + - katedec: decodes Kate streams to text + - kateenc: encodes Kate streams from text and SPU images + - kateparse: parses Kate streams + - katetag: allows changing metadata in Kate streams + - tiger: decodes and renders Kate streams using libtiger (needs libtiger) + +More information about Kate can be found at: +http://wiki.xiph.org/index.php/OggKate + diff --git a/ext/kate/gstkate.c b/ext/kate/gstkate.c new file mode 100644 index 0000000000..5e60d18710 --- /dev/null +++ b/ext/kate/gstkate.c @@ -0,0 +1,150 @@ +/* + * GStreamer + * Copyright 2005 Thomas Vander Stichele + * Copyright 2005 Ronald S. Bultje + * Copyright 2008 Vincent Penquerc'h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "gstkate.h" +#include "gstkatedec.h" +#include "gstkateenc.h" +#include "gstkateparse.h" +#include "gstkatetag.h" +#ifdef HAVE_TIGER +#include "gstkatetiger.h" +#endif + +GST_DEBUG_CATEGORY (gst_katedec_debug); +GST_DEBUG_CATEGORY (gst_kateenc_debug); +GST_DEBUG_CATEGORY (gst_kateparse_debug); +GST_DEBUG_CATEGORY (gst_katetag_debug); +#ifdef HAVE_TIGER +GST_DEBUG_CATEGORY (gst_katetiger_debug); +#endif + + +static GstStaticCaps kate_caps = GST_STATIC_CAPS (GST_KATE_MIME_TYPE); + +#define KATE_CAPS (gst_static_caps_get(&kate_caps)) +static void +gst_kate_type_find (GstTypeFind * tf, gpointer private) +{ + guint8 *data = gst_type_find_peek (tf, 0, 8); + + if (data) { + if (memcmp (data, "\200kate\0\0\0", 8) != 0) + return; + + gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, KATE_CAPS); + } +} + +/* entry point to initialize the plug-in + * initialize the plug-in itself + * register the element factories and pad templates + * register the features + */ +static gboolean +plugin_init (GstPlugin * plugin) +{ + GstCaps *caps; + + caps = gst_caps_new_simple (GST_KATE_MIME_TYPE, NULL); + if (!gst_type_find_register (plugin, GST_KATE_MIME_TYPE, GST_RANK_PRIMARY, + gst_kate_type_find, NULL, caps, NULL, NULL)) { + GST_WARNING ("kate: failed to register typefind"); + gst_caps_unref (caps); + return FALSE; + } + gst_caps_unref (caps); + + GST_DEBUG_CATEGORY_INIT (gst_katedec_debug, "katedec", 0, "Kate decoder"); + GST_DEBUG_CATEGORY_INIT (gst_kateenc_debug, "kateenc", 0, "Kate encoder"); + GST_DEBUG_CATEGORY_INIT (gst_kateparse_debug, "kateparse", 0, "Kate parser"); + GST_DEBUG_CATEGORY_INIT (gst_katetag_debug, "katetag", 0, "Kate tagger"); +#ifdef HAVE_TIGER + GST_DEBUG_CATEGORY_INIT (gst_katetiger_debug, "tiger", 0, + "Kate Tiger renderer"); +#endif + + /* if we don't build tiger, we'll want to autoplug and convert to text, + but if we do build tiger, we'll want to use it preferentially as it + can play non text streams too */ + if (!gst_element_register (plugin, "katedec", GST_RANK_PRIMARY, + GST_TYPE_KATE_DEC)) + return FALSE; + + if (!gst_element_register (plugin, "kateenc", GST_RANK_NONE, + GST_TYPE_KATE_ENC)) + return FALSE; + + if (!gst_element_register (plugin, "kateparse", GST_RANK_NONE, + GST_TYPE_KATE_PARSE)) + return FALSE; + + if (!gst_element_register (plugin, "katetag", GST_RANK_NONE, + GST_TYPE_KATE_TAG)) + return FALSE; + +#ifdef HAVE_TIGER + if (!gst_element_register (plugin, "tiger", GST_RANK_NONE, + GST_TYPE_KATE_TIGER)) + return FALSE; +#endif + + return TRUE; +} + +/* this is the structure that gstreamer looks for to register plugins + */ +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "kate", + "Kate plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/kate/gstkate.h b/ext/kate/gstkate.h new file mode 100644 index 0000000000..ee69590f9b --- /dev/null +++ b/ext/kate/gstkate.h @@ -0,0 +1,55 @@ +/* + * GStreamer + * Copyright (C) 2008 Vincent Penquerc'h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_KATE_H__ +#define __GST_KATE_H__ + +#include + +G_BEGIN_DECLS + +#define GST_KATE_MIME_TYPE "application/x-kate" + +G_END_DECLS + +#endif /* __GST_KATE_H__ */ diff --git a/ext/kate/gstkatedec.c b/ext/kate/gstkatedec.c new file mode 100644 index 0000000000..851bab2c13 --- /dev/null +++ b/ext/kate/gstkatedec.c @@ -0,0 +1,318 @@ +/* + * GStreamer + * Copyright 2005 Thomas Vander Stichele + * Copyright 2005 Ronald S. Bultje + * Copyright 2008 Vincent Penquerc'h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-katedec + * @see_also: oggdemux + * + * + * + * This element decodes Kate streams + * Kate is a free codec + * for text based data, such as subtitles. Any number of kate streams can be + * embedded in an Ogg stream. + * + * + * libkate (see above url) is needed to build this plugin. + * + * Example pipeline + * + * This explicitely decodes a Kate stream: + * + * gst-launch filesrc location=test.ogg ! oggdemux ! katedec ! fakesink silent=TRUE + * + * + * + * This will automatically detect and use any Kate streams multiplexed + * in an Ogg stream: + * + * gst-launch playbin uri=file:///tmp/test.ogg + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "gstkate.h" +#include "gstkatedec.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_katedec_debug); +#define GST_CAT_DEFAULT gst_katedec_debug + +/* Filter signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + ARG_REMOVE_MARKUP = DECODER_BASE_ARG_COUNT +}; + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_KATE_MIME_TYPE) + ); + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("text/plain; text/x-pango-markup") + ); + +GST_BOILERPLATE (GstKateDec, gst_kate_dec, GstElement, GST_TYPE_ELEMENT); + +static void gst_kate_dec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_kate_dec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstFlowReturn gst_kate_dec_chain (GstPad * pad, GstBuffer * buf); +static GstStateChangeReturn gst_kate_dec_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_kate_dec_sink_query (GstPad * pad, GstQuery * query); + +static void +gst_kate_dec_base_init (gpointer gclass) +{ + static GstElementDetails element_details = + GST_ELEMENT_DETAILS ("Kate stream text decoder", + "Codec/Decoder/Subtitle", + "Decodes Kate text streams", + "Vincent Penquerc'h "); + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory)); + gst_element_class_set_details (element_class, &element_details); +} + +/* initialize the plugin's class */ +static void +gst_kate_dec_class_init (GstKateDecClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_kate_dec_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_kate_dec_get_property); + + gst_kate_util_install_decoder_base_properties (gobject_class); + + g_object_class_install_property (gobject_class, ARG_REMOVE_MARKUP, + g_param_spec_boolean ("remove-markup", "Remove markup", + "Remove markup from decoded text ?", FALSE, G_PARAM_READWRITE)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_kate_dec_change_state); +} + +/* initialize the new element + * instantiate pads and add them to element + * set functions + * initialize structure + */ +static void +gst_kate_dec_init (GstKateDec * dec, GstKateDecClass * gclass) +{ + GST_DEBUG_OBJECT (dec, "gst_kate_dec_init"); + + dec->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink"); + gst_pad_set_chain_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_kate_dec_chain)); + gst_pad_set_query_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_kate_dec_sink_query)); + gst_pad_use_fixed_caps (dec->sinkpad); + gst_pad_set_caps (dec->sinkpad, + gst_static_pad_template_get_caps (&sink_factory)); + gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad); + + dec->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); + gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad); + + gst_kate_util_decode_base_init (&dec->decoder); + + dec->remove_markup = FALSE; +} + +static void +gst_kate_dec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_kate_dec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstKateDec *kd = GST_KATE_DEC (object); + + switch (prop_id) { + case ARG_REMOVE_MARKUP: + g_value_set_boolean (value, kd->remove_markup); + break; + default: + if (!gst_kate_util_decoder_base_get_property (&kd->decoder, object, + prop_id, value, pspec)) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +/* GstElement vmethod implementations */ + +/* chain function + * this function does the actual processing + */ + +static GstFlowReturn +gst_kate_dec_chain (GstPad * pad, GstBuffer * buf) +{ + GstKateDec *kd = GST_KATE_DEC (gst_pad_get_parent (pad)); + const kate_event *ev = NULL; + GstFlowReturn rflow = GST_FLOW_OK; + + rflow = + gst_kate_util_decoder_base_chain_kate_packet (&kd->decoder, + GST_ELEMENT_CAST (kd), pad, buf, kd->srcpad, &ev); + if (G_UNLIKELY (rflow != GST_FLOW_OK)) { + gst_object_unref (kd); + gst_buffer_unref (buf); + return rflow; + } + + if (ev) { + gchar *escaped; + GstBuffer *buffer; + size_t len; + + if (kd->remove_markup && ev->text_markup_type != kate_markup_none) { + size_t len0 = ev->len + 1; + escaped = g_strdup (ev->text); + if (escaped) { + kate_text_remove_markup (ev->text_encoding, escaped, &len0); + } + } else if (ev->text_markup_type == kate_markup_none) { + /* no pango markup yet, escape text */ + /* TODO: actually do the pango thing */ + escaped = g_markup_printf_escaped ("%s", ev->text); + } else { + escaped = g_strdup (ev->text); + } + + if (G_LIKELY (escaped)) { + len = strlen (escaped); + GST_DEBUG_OBJECT (kd, "kate event: %s, escaped %s", ev->text, escaped); + buffer = gst_buffer_new_and_alloc (len + 1); + if (G_LIKELY (buffer)) { + /* allocate and copy the NULs, but don't include them in passed size */ + memcpy (GST_BUFFER_DATA (buffer), escaped, len + 1); + GST_BUFFER_SIZE (buffer) = len; + GST_BUFFER_TIMESTAMP (buffer) = ev->start_time * GST_SECOND; + GST_BUFFER_DURATION (buffer) = + (ev->end_time - ev->start_time) * GST_SECOND; + gst_buffer_set_caps (buffer, GST_PAD_CAPS (kd->srcpad)); + rflow = gst_pad_push (kd->srcpad, buffer); + if (rflow == GST_FLOW_NOT_LINKED) { + GST_DEBUG_OBJECT (kd, "source pad not linked, ignored"); + } else if (rflow != GST_FLOW_OK) { + GST_WARNING_OBJECT (kd, "failed to push buffer: %s", + gst_flow_get_name (rflow)); + } + } else { + GST_WARNING_OBJECT (kd, "failed to create buffer"); + rflow = GST_FLOW_ERROR; + } + g_free (escaped); + } else { + GST_WARNING_OBJECT (kd, "failed to allocate string"); + rflow = GST_FLOW_ERROR; + } + } + + gst_object_unref (kd); + gst_buffer_unref (buf); + return rflow; +} + +static GstStateChangeReturn +gst_kate_dec_change_state (GstElement * element, GstStateChange transition) +{ + GstKateDec *kd = GST_KATE_DEC (element); + return gst_kate_decoder_base_change_state (&kd->decoder, element, + parent_class, transition); +} + +gboolean +gst_kate_dec_sink_query (GstPad * pad, GstQuery * query) +{ + GstKateDec *kd = GST_KATE_DEC (gst_pad_get_parent (pad)); + gboolean res = + gst_kate_decoder_base_sink_query (&kd->decoder, GST_ELEMENT_CAST (kd), + pad, query); + gst_object_unref (kd); + return res; +} diff --git a/ext/kate/gstkatedec.h b/ext/kate/gstkatedec.h new file mode 100644 index 0000000000..a7011ecbcd --- /dev/null +++ b/ext/kate/gstkatedec.h @@ -0,0 +1,88 @@ +/* + * GStreamer + * Copyright 2005 Thomas Vander Stichele + * Copyright 2005 Ronald S. Bultje + * Copyright 2008 Vincent Penquerc'h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_KATE_DEC_H__ +#define __GST_KATE_DEC_H__ + +#include +#include +#include "gstkateutil.h" + +G_BEGIN_DECLS +/* #defines don't like whitespacey bits */ +#define GST_TYPE_KATE_DEC \ + (gst_kate_dec_get_type()) +#define GST_KATE_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_KATE_DEC,GstKateDec)) +#define GST_KATE_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_KATE,GstKateDecClass)) +#define GST_IS_KATE_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_KATE_DEC)) +#define GST_IS_KATE_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_KATE_DEC)) +typedef struct _GstKateDec GstKateDec; +typedef struct _GstKateDecClass GstKateDecClass; + +struct _GstKateDec +{ + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + + GstKateDecoderBase decoder; + + gboolean remove_markup; +}; + +struct _GstKateDecClass +{ + GstElementClass parent_class; +}; + +GType gst_kate_dec_get_type (void); + +G_END_DECLS +#endif /* __GST_KATE_DEC_H__ */ diff --git a/ext/kate/gstkateenc.c b/ext/kate/gstkateenc.c new file mode 100644 index 0000000000..26eff02bca --- /dev/null +++ b/ext/kate/gstkateenc.c @@ -0,0 +1,1834 @@ +/* + * GStreamer + * Copyright 2005 Thomas Vander Stichele + * Copyright 2005 Ronald S. Bultje + * Copyright (C) 2007 Fluendo S.A. + * Copyright 2008 Vincent Penquerc'h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-kateenc + * @see_also: oggmux + * + * + * + * This element encodes Kate streams + * Kate is a free codec + * for text based data, such as subtitles. Any number of kate streams can be + * embedded in an Ogg stream. + * + * + * libkate (see above url) is needed to build this plugin. + * + * Example pipeline + * + * This encodes a DVD SPU track to a Kate stream: + * + * gst-launch dvdreadsrc ! dvddemux ! dvdsubparse ! kateenc ! oggmux ! filesink location=test.ogg + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#include "gstkate.h" +#include "gstkateutil.h" +#include "gstkateenc.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_kateenc_debug); +#define GST_CAT_DEFAULT gst_kateenc_debug + +/* Filter signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + ARG_0, + ARG_LANGUAGE, + ARG_CATEGORY, + ARG_GRANULE_RATE_NUM, + ARG_GRANULE_RATE_DEN, + ARG_GRANULE_SHIFT, + ARG_KEEPALIVE_MIN_TIME, + ARG_ORIGINAL_CANVAS_WIDTH, + ARG_ORIGINAL_CANVAS_HEIGHT, + ARG_DEFAULT_SPU_DURATION, +}; + +/* taken off the dvdsubdec element */ +static const guint32 gst_kate_enc_default_clut[16] = { + 0xb48080, 0x248080, 0x628080, 0xd78080, + 0x808080, 0x808080, 0x808080, 0x808080, + 0x808080, 0x808080, 0x808080, 0x808080, + 0x808080, 0x808080, 0x808080, 0x808080 +}; + +#define GST_KATE_UINT16_BE(ptr) ( ( ((guint16)((ptr)[0])) <<8) | ((ptr)[1]) ) + +/* taken off the DVD SPU decoder - now is time for today's WTF ???? */ +#define GST_KATE_STM_TO_GST(stm) ((GST_MSECOND * 1024 * (stm)) / 90) + +#define DEFAULT_KEEPALIVE_MIN_TIME 2.5f +#define DEFAULT_DEFAULT_SPU_DURATION 1.5f + +#define GST_KATE_SPU_MIME_TYPE "video/x-dvd-subpicture" + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("text/plain; text/x-pango-markup; " GST_KATE_SPU_MIME_TYPE) + ); + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_KATE_MIME_TYPE) + ); + +static void gst_kate_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_kate_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_kate_enc_dispose (GObject * object); + +static GstFlowReturn gst_kate_enc_chain (GstPad * pad, GstBuffer * buf); +static GstStateChangeReturn gst_kate_enc_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_kate_enc_sink_event (GstPad * pad, GstEvent * event); +static const GstQueryType *gst_kate_enc_source_query_type (GstPad * pad); +static gboolean gst_kate_enc_source_query (GstPad * pad, GstQuery * query); +static void gst_kate_enc_add_interfaces (GType kateenc_type); + +GST_BOILERPLATE_FULL (GstKateEnc, gst_kate_enc, GstElement, + GST_TYPE_ELEMENT, gst_kate_enc_add_interfaces); + +static void +gst_kate_enc_base_init (gpointer gclass) +{ + static const GstElementDetails element_details = + GST_ELEMENT_DETAILS ("Kate stream encoder", + "Codec/Encoder/Subtitle", + "Encodes Kate streams from text or subpictures", + "Vincent Penquerc'h "); + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory)); + gst_element_class_set_details (element_class, &element_details); +} + +/* initialize the plugin's class */ +static void +gst_kate_enc_class_init (GstKateEncClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_kate_enc_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_kate_enc_get_property); + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_kate_enc_dispose); + + g_object_class_install_property (gobject_class, ARG_LANGUAGE, + g_param_spec_string ("language", "Language", + "Set the language of the stream", "", G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_CATEGORY, + g_param_spec_string ("category", "Category", + "Set the category of the stream", "", G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_GRANULE_RATE_NUM, + g_param_spec_int ("granule-rate-numerator", "Granule rate numerator", + "Set the numerator of the granule rate", + 1, G_MAXINT, 1, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_GRANULE_RATE_DEN, + g_param_spec_int ("granule-rate-denominator", "Granule rate denominator", + "Set the denominator of the granule rate", + 1, G_MAXINT, 1000, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_GRANULE_SHIFT, + g_param_spec_int ("granule-shift", "Granule shift", + "Set the granule shift", 0, 64, 32, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_ORIGINAL_CANVAS_WIDTH, + g_param_spec_int ("original-canvas-width", "Original canvas width", + "Set the width of the canvas this stream was authored for (0 is unspecified)", + 0, G_MAXINT, 0, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_ORIGINAL_CANVAS_HEIGHT, + g_param_spec_int ("original-canvas-height", "Original canvas height", + "Set the height of the canvas this stream was authored for (0 is unspecified)", + 0, G_MAXINT, 0, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_KEEPALIVE_MIN_TIME, + g_param_spec_float ("keepalive-min-time", "Keepalive mimimum time", + "Set minimum time to emit keepalive packets (0 disables keepalive packets)", + 0.0f, FLT_MAX, DEFAULT_KEEPALIVE_MIN_TIME, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_SPU_DURATION, + g_param_spec_float ("default-spu-duration", "Default SPU duration", + "Set the assumed max duration (in seconds) of SPUs with no duration specified", + 0.0f, FLT_MAX, DEFAULT_DEFAULT_SPU_DURATION, G_PARAM_READWRITE)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_kate_enc_change_state); +} + +static void +gst_kate_enc_add_interfaces (GType kateenc_type) +{ + static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL }; + + g_type_add_interface_static (kateenc_type, GST_TYPE_TAG_SETTER, + &tag_setter_info); +} + +/* initialize the new element + * instantiate pads and add them to element + * set functions + * initialize structure + */ +static void +gst_kate_enc_init (GstKateEnc * ke, GstKateEncClass * gclass) +{ + GST_DEBUG_OBJECT (ke, "gst_kate_enc_init"); + + ke->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink"); + gst_pad_set_chain_function (ke->sinkpad, + GST_DEBUG_FUNCPTR (gst_kate_enc_chain)); + gst_pad_set_event_function (ke->sinkpad, + GST_DEBUG_FUNCPTR (gst_kate_enc_sink_event)); + gst_element_add_pad (GST_ELEMENT (ke), ke->sinkpad); + + ke->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); + gst_pad_set_query_type_function (ke->srcpad, + GST_DEBUG_FUNCPTR (gst_kate_enc_source_query_type)); + gst_pad_set_query_function (ke->srcpad, + GST_DEBUG_FUNCPTR (gst_kate_enc_source_query)); + gst_element_add_pad (GST_ELEMENT (ke), ke->srcpad); + + ke->initialized = FALSE; + ke->headers_sent = FALSE; + ke->last_timestamp = 0; + ke->latest_end_time = 0; + ke->language = NULL; + ke->category = NULL; + ke->granule_rate_numerator = 1000; + ke->granule_rate_denominator = 1; + ke->granule_shift = 32; + ke->original_canvas_width = 0; + ke->original_canvas_height = 0; + ke->keepalive_min_time = DEFAULT_KEEPALIVE_MIN_TIME; + ke->default_spu_duration = DEFAULT_DEFAULT_SPU_DURATION; + memcpy (ke->spu_clut, gst_kate_enc_default_clut, + sizeof (gst_kate_enc_default_clut)); + ke->delayed_spu = FALSE; +} + +static void +gst_kate_enc_dispose (GObject * object) +{ + GstKateEnc *ke = GST_KATE_ENC (object); + + GST_LOG_OBJECT (ke, "disposing"); + + if (ke->language) { + g_free (ke->language); + ke->language = NULL; + } + if (ke->category) { + g_free (ke->category); + ke->category = NULL; + } + + GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); +} + +static void +gst_kate_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstKateEnc *ke = GST_KATE_ENC (object); + const char *str; + + switch (prop_id) { + case ARG_LANGUAGE: + if (ke->language) { + g_free (ke->language); + ke->language = NULL; + } + str = g_value_get_string (value); + if (str) + ke->language = g_strdup (str); + break; + case ARG_CATEGORY: + if (ke->category) { + g_free (ke->category); + ke->category = NULL; + } + str = g_value_get_string (value); + if (str) + ke->category = g_strdup (str); + break; + case ARG_GRANULE_RATE_NUM: + ke->granule_rate_numerator = g_value_get_int (value); + break; + case ARG_GRANULE_RATE_DEN: + ke->granule_rate_denominator = g_value_get_int (value); + break; + case ARG_GRANULE_SHIFT: + ke->granule_rate_denominator = g_value_get_int (value); + break; + case ARG_KEEPALIVE_MIN_TIME: + ke->keepalive_min_time = g_value_get_float (value); + break; + case ARG_ORIGINAL_CANVAS_WIDTH: + ke->original_canvas_width = g_value_get_int (value); + break; + case ARG_ORIGINAL_CANVAS_HEIGHT: + ke->original_canvas_height = g_value_get_int (value); + break; + case ARG_DEFAULT_SPU_DURATION: + ke->default_spu_duration = g_value_get_float (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_kate_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstKateEnc *ke = GST_KATE_ENC (object); + + switch (prop_id) { + case ARG_LANGUAGE: + g_value_set_string (value, ke->language ? ke->language : ""); + break; + case ARG_CATEGORY: + g_value_set_string (value, ke->category ? ke->category : ""); + break; + case ARG_GRANULE_RATE_NUM: + g_value_set_int (value, ke->granule_rate_numerator); + break; + case ARG_GRANULE_RATE_DEN: + g_value_set_int (value, ke->granule_rate_denominator); + break; + case ARG_GRANULE_SHIFT: + g_value_set_int (value, ke->granule_shift); + break; + case ARG_KEEPALIVE_MIN_TIME: + g_value_set_float (value, ke->keepalive_min_time); + break; + case ARG_ORIGINAL_CANVAS_WIDTH: + g_value_set_int (value, ke->original_canvas_width); + break; + case ARG_ORIGINAL_CANVAS_HEIGHT: + g_value_set_int (value, ke->original_canvas_height); + break; + case ARG_DEFAULT_SPU_DURATION: + g_value_set_float (value, ke->default_spu_duration); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* GstElement vmethod implementations */ + +static GstBuffer * +gst_kate_enc_create_buffer (GstKateEnc * ke, kate_packet * kp, + kate_int64_t granpos, GstClockTime timestamp, GstClockTime duration, + gboolean header) +{ + GstBuffer *buffer; + + buffer = gst_buffer_new_and_alloc (kp->nbytes); + if (G_UNLIKELY (!buffer)) { + GST_WARNING_OBJECT (ke, "Failed to allocate buffer for %u bytes", + kp->nbytes); + return NULL; + } + + memcpy (GST_BUFFER_DATA (buffer), kp->data, kp->nbytes); + + /* same system as other Ogg codecs, as per ext/ogg/README: + OFFSET_END is the granulepos + OFFSET is its time representation + */ + GST_BUFFER_OFFSET_END (buffer) = granpos; + GST_BUFFER_OFFSET (buffer) = timestamp; + GST_BUFFER_TIMESTAMP (buffer) = timestamp; + GST_BUFFER_DURATION (buffer) = duration; + + /* data packets are each on their own page */ +// if (!header) +// GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + + return buffer; +} + +static GstFlowReturn +gst_kate_enc_push_buffer (GstKateEnc * ke, GstBuffer * buffer) +{ + GstFlowReturn rflow; + + ke->last_timestamp = GST_BUFFER_TIMESTAMP (buffer); + if (GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) > + ke->latest_end_time) { + ke->latest_end_time = + GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer); + } + + /* Hack to flush each packet on its own page - taken off the CMML encoder element */ + GST_BUFFER_DURATION (buffer) = G_MAXINT64; + + rflow = gst_pad_push (ke->srcpad, buffer); + if (G_UNLIKELY (rflow != GST_FLOW_OK)) { + GST_ERROR_OBJECT (ke, "Failed to push buffer: %d", rflow); + } + + return rflow; +} + +static GstFlowReturn +gst_kate_enc_push_and_free_kate_packet (GstKateEnc * ke, kate_packet * kp, + kate_int64_t granpos, GstClockTime timestamp, GstClockTime duration, + gboolean header) +{ + GstBuffer *buffer; + + GST_LOG_OBJECT (ke, "Creating buffer, %u bytes", kp->nbytes); + buffer = + gst_kate_enc_create_buffer (ke, kp, granpos, timestamp, duration, header); + if (G_UNLIKELY (!buffer)) { + GST_WARNING_OBJECT (ke, "Failed to create buffer, %u bytes", kp->nbytes); + kate_packet_clear (kp); + return GST_FLOW_ERROR; + } + + kate_packet_clear (kp); + + return gst_kate_enc_push_buffer (ke, buffer); +} + +static void +gst_kate_enc_metadata_set1 (const GstTagList * list, const gchar * tag, + gpointer kateenc) +{ + GstKateEnc *ke = GST_KATE_ENC (kateenc); + GList *vc_list, *l; + + vc_list = gst_tag_to_vorbis_comments (list, tag); + + for (l = vc_list; l != NULL; l = l->next) { + const gchar *vc_string = (const gchar *) l->data; + gchar *key = NULL, *val = NULL; + + GST_LOG_OBJECT (ke, "Kate comment: %s", vc_string); + if (gst_tag_parse_extended_comment (vc_string, &key, NULL, &val, TRUE)) { + kate_comment_add_tag (&ke->kc, key, val); + g_free (key); + g_free (val); + } + } + + g_list_foreach (vc_list, (GFunc) g_free, NULL); + g_list_free (vc_list); +} + +static void +gst_kate_enc_set_metadata (GstKateEnc * ke) +{ + GstTagList *merged_tags; + const GstTagList *user_tags; + + user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (ke)); + + GST_DEBUG_OBJECT (ke, "upstream tags = %" GST_PTR_FORMAT, ke->tags); + GST_DEBUG_OBJECT (ke, "user-set tags = %" GST_PTR_FORMAT, user_tags); + + /* gst_tag_list_merge() will handle NULL for either or both lists fine */ + merged_tags = gst_tag_list_merge (user_tags, ke->tags, + gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (ke))); + + if (merged_tags) { + GST_DEBUG_OBJECT (ke, "merged tags = %" GST_PTR_FORMAT, merged_tags); + gst_tag_list_foreach (merged_tags, gst_kate_enc_metadata_set1, ke); + gst_tag_list_free (merged_tags); + } +} + +static GstFlowReturn +gst_kate_enc_send_headers (GstKateEnc * ke) +{ + GstFlowReturn rflow = GST_FLOW_OK; + GstCaps *caps; + GList *headers = NULL, *item; + + gst_kate_enc_set_metadata (ke); + + /* encode headers and store them in a list */ + while (1) { + kate_packet kp; + int ret = kate_encode_headers (&ke->k, &ke->kc, &kp); + if (ret == 0) { + GstBuffer *buffer = + gst_kate_enc_create_buffer (ke, &kp, 0ll, 0ll, 0ll, TRUE); + if (!buffer) { + rflow = GST_FLOW_ERROR; + break; + } + kate_packet_clear (&kp); + + headers = g_list_append (headers, buffer); + } else if (ret > 0) { + GST_LOG_OBJECT (ke, "Last header encoded"); + break; + } else { + GST_LOG_OBJECT (ke, "Error encoding header: %d", ret); + rflow = GST_FLOW_ERROR; + break; + } + } + + if (rflow == GST_FLOW_OK) { + caps = + gst_kate_util_set_header_on_caps (&ke->element, + gst_pad_get_caps (ke->srcpad), headers); + if (caps) { + GST_DEBUG_OBJECT (ke, "here are the caps: %" GST_PTR_FORMAT, caps); + gst_pad_set_caps (ke->srcpad, caps); + + GST_LOG_OBJECT (ke, "setting caps on headers"); + item = headers; + while (item) { + GstBuffer *buffer = item->data; + GST_LOG_OBJECT (ke, "settings caps on header %p", buffer); + gst_buffer_set_caps (buffer, caps); + item = item->next; + } + + gst_caps_unref (caps); + + GST_LOG_OBJECT (ke, "pushing headers"); + item = headers; + while (item) { + GstBuffer *buffer = item->data; + GST_LOG_OBJECT (ke, "pushing header %p", buffer); + gst_kate_enc_push_buffer (ke, buffer); + item = item->next; + } + } else { + GST_ERROR_OBJECT (ke, "Failed to set headers on caps"); + } + } + + g_list_free (headers); + + return rflow; +} + +static GstFlowReturn +gst_kate_enc_flush_headers (GstKateEnc * ke) +{ + GstFlowReturn rflow = GST_FLOW_OK; + if (!ke->headers_sent) { + GST_INFO_OBJECT (ke, "headers not yet sent, flushing"); + rflow = gst_kate_enc_send_headers (ke); + if (rflow == GST_FLOW_OK) { + ke->headers_sent = TRUE; + GST_INFO_OBJECT (ke, "headers flushed"); + } else { + GST_WARNING_OBJECT (ke, "Failed to flush headers: %d", rflow); + } + } + return rflow; +} + +enum SpuCmd +{ + SPU_CMD_FSTA_DSP = 0x00, /* Forced Display */ + SPU_CMD_DSP = 0x01, /* Display Start */ + SPU_CMD_STP_DSP = 0x02, /* Display Off */ + SPU_CMD_SET_COLOR = 0x03, /* Set the color indexes for the palette */ + SPU_CMD_SET_ALPHA = 0x04, /* Set the alpha indexes for the palette */ + SPU_CMD_SET_DAREA = 0x05, /* Set the display area for the SPU */ + SPU_CMD_DSPXA = 0x06, /* Pixel data addresses */ + SPU_CMD_CHG_COLCON = 0x07, /* Change Color & Contrast */ + SPU_CMD_END = 0xff +}; + +static void +gst_kate_enc_decode_colormap (GstKateEnc * ke, const guint8 * ptr) +{ + ke->spu_colormap[3] = ptr[0] >> 4; + ke->spu_colormap[2] = ptr[0] & 0x0f; + ke->spu_colormap[1] = ptr[1] >> 4; + ke->spu_colormap[0] = ptr[1] & 0x0f; +} + +static void +gst_kate_enc_decode_alpha (GstKateEnc * ke, const guint8 * ptr) +{ + ke->spu_alpha[3] = ptr[0] >> 4; + ke->spu_alpha[2] = ptr[0] & 0x0f; + ke->spu_alpha[1] = ptr[1] >> 4; + ke->spu_alpha[0] = ptr[1] & 0x0f; +} + +static void +gst_kate_enc_decode_area (GstKateEnc * ke, const guint8 * ptr) +{ + ke->spu_left = ((((guint16) ptr[0]) & 0x3f) << 4) | (ptr[1] >> 4); + ke->spu_top = ((((guint16) ptr[3]) & 0x3f) << 4) | (ptr[4] >> 4); + ke->spu_right = ((((guint16) ptr[1]) & 0x03) << 8) | ptr[2]; + ke->spu_bottom = ((((guint16) ptr[4]) & 0x03) << 8) | ptr[5]; + GST_DEBUG_OBJECT (ke, "SPU area %u %u -> %u %d", ke->spu_left, ke->spu_top, + ke->spu_right, ke->spu_bottom); +} + +static void +gst_kate_enc_decode_pixaddr (GstKateEnc * ke, const guint8 * ptr) +{ + ke->spu_pix_data[0] = GST_KATE_UINT16_BE (ptr + 0); + ke->spu_pix_data[1] = GST_KATE_UINT16_BE (ptr + 2); +} + +/* heavily inspired from dvdspudec */ +static guint16 +gst_kate_enc_decode_colcon (GstKateEnc * ke, const guint8 * ptr) +{ + guint16 nbytes = GST_KATE_UINT16_BE (ptr + 0); + guint16 nbytes_left = nbytes; + + GST_LOG_OBJECT (ke, "Number of bytes in color/contrast change command is %u", + nbytes); + if (G_UNLIKELY (nbytes < 2)) { + GST_WARNING_OBJECT (ke, + "Number of bytes in color/contrast change command is %u, should be at least 2", + nbytes); + return 0; + } + + ptr += 2; + nbytes_left -= 2; + + /* we will just skip that data for now */ + while (nbytes_left > 0) { + guint32 entry, nchanges, sz; + GST_LOG_OBJECT (ke, "Reading a color/contrast change entry, %u bytes left", + nbytes_left); + if (G_UNLIKELY (nbytes_left < 4)) { + GST_WARNING_OBJECT (ke, + "Not enough bytes to read a full color/contrast entry header"); + break; + } + entry = GST_READ_UINT32_BE (ptr); + GST_LOG_OBJECT (ke, "Color/contrast change entry header is %08x", entry); + nchanges = CLAMP ((ptr[2] >> 4), 1, 8); + ptr += 4; + nbytes_left -= 4; + if (entry == 0x0fffffff) { + GST_LOG_OBJECT (ke, + "Encountered color/contrast change termination code, breaking, %u bytes left", + nbytes_left); + break; + } + GST_LOG_OBJECT (ke, "Color/contrast change entry has %u changes", nchanges); + sz = 6 * nchanges; + if (G_UNLIKELY (sz > nbytes_left)) { + GST_WARNING_OBJECT (ke, + "Not enough bytes to read a full color/contrast entry"); + break; + } + ptr += sz; + nbytes_left -= sz; + } + return nbytes - nbytes_left; +} + +static inline guint8 +gst_kate_enc_get_nybble (const guint8 * nybbles, size_t * nybble_offset) +{ + guint8 ret; + + ret = nybbles[(*nybble_offset) / 2]; + + /* If the offset is even, we shift the answer down 4 bits, otherwise not */ + if ((*nybble_offset) & 0x01) + ret &= 0x0f; + else + ret = ret >> 4; + + (*nybble_offset)++; + + return ret; +} + +static guint16 +gst_kate_enc_get_rle_code (const guint8 * nybbles, size_t * nybble_offset) +{ + guint16 code; + + code = gst_kate_enc_get_nybble (nybbles, nybble_offset); + if (code < 0x4) { /* 4 .. f */ + code = (code << 4) | gst_kate_enc_get_nybble (nybbles, nybble_offset); + if (code < 0x10) { /* 1x .. 3x */ + code = (code << 4) | gst_kate_enc_get_nybble (nybbles, nybble_offset); + if (code < 0x40) { /* 04x .. 0fx */ + code = (code << 4) | gst_kate_enc_get_nybble (nybbles, nybble_offset); + } + } + } + return code; +} + +static void +gst_kate_enc_crop_bitmap (GstKateEnc * ke, kate_bitmap * kb, guint16 * dx, + guint16 * dy) +{ + int top, bottom, left, right; + guint8 zero = 0; + size_t n, x, y, w, h; + +#if 0 + /* find the zero */ + zero = kb->pixels[0]; + for (x = 0; x < kb->width; ++x) { + if (kb->pixels[x] != zero) { + GST_LOG_OBJECT (ke, "top line at %u is not zero: %u", x, kb->pixels[x]); + return; + } + } +#endif + + /* top */ + for (top = 0; top < kb->height; ++top) { + int empty = 1; + for (x = 0; x < kb->width; ++x) { + if (G_UNLIKELY (kb->pixels[x + top * kb->width] != zero)) { + empty = 0; + break; + } + } + if (!empty) + break; + } + + /* bottom */ + for (bottom = kb->height - 1; bottom >= top; --bottom) { + int empty = 1; + for (x = 0; x < kb->width; ++x) { + if (G_UNLIKELY (kb->pixels[x + bottom * kb->width] != zero)) { + empty = 0; + break; + } + } + if (!empty) + break; + } + + /* left */ + for (left = 0; left < kb->width; ++left) { + int empty = 1; + for (y = top; y <= bottom; ++y) { + if (G_UNLIKELY (kb->pixels[left + y * kb->width] != zero)) { + empty = 0; + break; + } + } + if (!empty) + break; + } + + /* right */ + for (right = kb->width - 1; right >= left; --right) { + int empty = 1; + for (y = top; y <= bottom; ++y) { + if (G_UNLIKELY (kb->pixels[right + y * kb->width] != zero)) { + empty = 0; + break; + } + } + if (!empty) + break; + } + + + w = right - left + 1; + h = bottom - top + 1; + GST_LOG_OBJECT (ke, "cropped from %zu %zu to %zu %zu", kb->width, kb->height, + w, h); + *dx += left; + *dy += top; + n = 0; + for (y = 0; y < h; ++y) { + memmove (kb->pixels + n, kb->pixels + kb->width * (y + top) + left, w); + n += w; + } + kb->width = w; + kb->height = h; +} + +#define CHECK(x) do { guint16 _ = (x); if (G_UNLIKELY((_) > sz)) { GST_WARNING_OBJECT (ke, "SPU overflow"); return GST_FLOW_ERROR; } } while (0) +#define ADVANCE(x) do { guint16 _ = (x); ptr += (_); sz -= (_); } while (0) +#define IGNORE(x) do { guint16 __ = (x); CHECK (__); ADVANCE (__); } while (0) + +static GstFlowReturn +gst_kate_enc_decode_command_sequence (GstKateEnc * ke, GstBuffer * buf, + guint16 command_sequence_offset) +{ + guint16 date; + guint16 next_command_sequence; + const guint8 *ptr; + guint16 sz; + + if (command_sequence_offset >= GST_BUFFER_SIZE (buf)) { + GST_WARNING_OBJECT (ke, "Command sequence offset %u is out of range %u", + command_sequence_offset, GST_BUFFER_SIZE (buf)); + return GST_FLOW_ERROR; + } + + ptr = GST_BUFFER_DATA (buf) + command_sequence_offset; + sz = GST_BUFFER_SIZE (buf) - command_sequence_offset; + + GST_DEBUG_OBJECT (ke, "Decoding command sequence at %u (%u bytes)", + command_sequence_offset, sz); + + CHECK (2); + date = GST_KATE_UINT16_BE (ptr); + ADVANCE (2); + GST_DEBUG_OBJECT (ke, "date %u", date); + + CHECK (2); + next_command_sequence = GST_KATE_UINT16_BE (ptr); + ADVANCE (2); + GST_DEBUG_OBJECT (ke, "next command sequence at %u", next_command_sequence); + + while (sz) { + guint8 cmd = *ptr++; + switch (cmd) { + case SPU_CMD_FSTA_DSP: /* 0x00 */ + GST_DEBUG_OBJECT (ke, "[0] DISPLAY"); + break; + case SPU_CMD_DSP: /* 0x01 */ + GST_DEBUG_OBJECT (ke, "[1] SHOW"); + ke->show_time = date; + break; + case SPU_CMD_STP_DSP: /* 0x02 */ + GST_DEBUG_OBJECT (ke, "[2] HIDE"); + ke->hide_time = date; + break; + case SPU_CMD_SET_COLOR: /* 0x03 */ + GST_DEBUG_OBJECT (ke, "[3] SET COLOR"); + CHECK (2); + gst_kate_enc_decode_colormap (ke, ptr); + ADVANCE (2); + break; + case SPU_CMD_SET_ALPHA: /* 0x04 */ + GST_DEBUG_OBJECT (ke, "[4] SET ALPHA"); + CHECK (2); + gst_kate_enc_decode_alpha (ke, ptr); + ADVANCE (2); + break; + case SPU_CMD_SET_DAREA: /* 0x05 */ + GST_DEBUG_OBJECT (ke, "[5] SET DISPLAY AREA"); + CHECK (6); + gst_kate_enc_decode_area (ke, ptr); + ADVANCE (6); + break; + case SPU_CMD_DSPXA: /* 0x06 */ + GST_DEBUG_OBJECT (ke, "[6] SET PIXEL ADDRESSES"); + CHECK (4); + gst_kate_enc_decode_pixaddr (ke, ptr); + GST_DEBUG_OBJECT (ke, " -> first pixel address %u", + ke->spu_pix_data[0]); + GST_DEBUG_OBJECT (ke, " -> second pixel address %u", + ke->spu_pix_data[1]); + ADVANCE (4); + break; + case SPU_CMD_CHG_COLCON: /* 0x07 */ + GST_DEBUG_OBJECT (ke, "[7] CHANGE COLOR/CONTRAST"); + CHECK (2); + ADVANCE (gst_kate_enc_decode_colcon (ke, ptr)); + break; + case SPU_CMD_END: /* 0xff */ + GST_DEBUG_OBJECT (ke, "[0xff] END"); + if (next_command_sequence != command_sequence_offset) { + GST_DEBUG_OBJECT (ke, "Jumping to next sequence at offset %u", + next_command_sequence); + return gst_kate_enc_decode_command_sequence (ke, buf, + next_command_sequence); + } else { + GST_DEBUG_OBJECT (ke, "No more sequences to decode"); + return GST_FLOW_OK; + } + break; + default: + GST_WARNING_OBJECT (ke, "invalid SPU command: %u", cmd); + return GST_FLOW_ERROR; + } + } + return GST_FLOW_ERROR; +} + +static inline int +gst_kate_enc_clamp (int value) +{ + if (value < 0) + return 0; + if (value > 255) + return 255; + return value; +} + +static void +gst_kate_enc_yuv2rgb (int y, int u, int v, int *r, int *g, int *b) +{ +#if 0 + *r = gst_kate_enc_clamp (y + 1.371 * v); + *g = gst_kate_enc_clamp (y - 0.698 * v - 0.336 * u); + *b = gst_kate_enc_clamp (y + 1.732 * u); +#elif 0 + *r = gst_kate_enc_clamp (y + u); + *g = gst_kate_enc_clamp (y - (76 * u - 26 * v) / 256); + *b = gst_kate_enc_clamp (y + v); +#else + y = (y - 16) * 255 / 219; + u -= 128; + v -= 128; + + *r = gst_kate_enc_clamp (y + 1.402 * 255 / 224 * v); + *g = gst_kate_enc_clamp (y + 0.34414 * 255 / 224 * v - + 0.71414 * 255 / 224 * u); + *b = gst_kate_enc_clamp (y + 1.772 * 244 / 224 * u); +#endif +} + +static GstFlowReturn +gst_kate_enc_create_spu_palette (GstKateEnc * ke, kate_palette * kp) +{ + size_t n; + + kate_palette_init (kp); + kp->ncolors = 4; + kp->colors = (kate_color *) g_malloc (kp->ncolors * sizeof (kate_color)); + if (G_UNLIKELY (!kp->colors)) + return GST_FLOW_ERROR; + +#if 1 + for (n = 0; n < kp->ncolors; ++n) { + int idx = ke->spu_colormap[n]; + guint32 color = ke->spu_clut[idx]; + int y = (color >> 16) & 0xff; + int v = (color >> 8) & 0xff; + int u = color & 0xff; + int r, g, b; + gst_kate_enc_yuv2rgb (y, u, v, &r, &g, &b); + kp->colors[n].r = r; + kp->colors[n].g = g; + kp->colors[n].b = b; + kp->colors[n].a = ke->spu_alpha[n] * 17; + } +#else + /* just make a ramp from 0 to 255 for those non transparent colors */ + for (n = 0; n < kp->ncolors; ++n) + if (ke->spu_alpha[n] == 0) + ++ntrans; + + for (n = 0; n < kp->ncolors; ++n) { + kp->colors[n].r = luma; + kp->colors[n].g = luma; + kp->colors[n].b = luma; + kp->colors[n].a = ke->spu_alpha[n] * 17; + if (ke->spu_alpha[n]) + luma /= 2; + } +#endif + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_kate_enc_decode_spu (GstKateEnc * ke, GstBuffer * buf, kate_region * kr, + kate_bitmap * kb, kate_palette * kp) +{ + const guint8 *ptr = GST_BUFFER_DATA (buf); + size_t sz = GST_BUFFER_SIZE (buf); + guint16 packet_size; + guint16 x, y; + size_t n; + guint8 *pixptr[2]; + size_t nybble_offset[2]; + size_t max_nybbles[2]; + GstFlowReturn rflow; + guint16 next_command_sequence; + guint16 code; + + /* before decoding anything, initialize to sensible defaults */ + memset (ke->spu_colormap, 0, sizeof (ke->spu_colormap)); + memset (ke->spu_alpha, 0, sizeof (ke->spu_alpha)); + ke->spu_top = ke->spu_left = 1; + ke->spu_bottom = ke->spu_right = 0; + ke->spu_pix_data[0] = ke->spu_pix_data[1] = 0; + ke->show_time = ke->hide_time = 0; + + /* read sizes and get to the start of the data */ + CHECK (2); + packet_size = GST_KATE_UINT16_BE (ptr); + ADVANCE (2); + GST_DEBUG_OBJECT (ke, "packet size %u (GstBuffer size %u)", packet_size, + GST_BUFFER_SIZE (buf)); + + CHECK (2); + next_command_sequence = GST_KATE_UINT16_BE (ptr); + ADVANCE (2); + ptr = GST_BUFFER_DATA (buf) + next_command_sequence; + sz = GST_BUFFER_SIZE (buf) - next_command_sequence; + GST_DEBUG_OBJECT (ke, "next command sequence at %u for %u", + next_command_sequence, sz); + + rflow = gst_kate_enc_decode_command_sequence (ke, buf, next_command_sequence); + if (G_UNLIKELY (rflow != GST_FLOW_OK)) + return rflow; + + /* if no addresses or sizes were given, or if they define an empty SPU, nothing more to do */ + if (G_UNLIKELY (ke->spu_right - ke->spu_left < 0 + || ke->spu_bottom - ke->spu_top < 0 || ke->spu_pix_data[0] == 0 + || ke->spu_pix_data[1] == 0)) { + GST_WARNING_OBJECT (ke, "SPU area is empty, nothing to encode"); + return GST_FLOW_ERROR; + } + + /* create the palette */ + rflow = gst_kate_enc_create_spu_palette (ke, kp); + if (G_UNLIKELY (rflow != GST_FLOW_OK)) + return rflow; + + /* create the bitmap */ + kate_bitmap_init (kb); + kb->width = ke->spu_right - ke->spu_left + 1; + kb->height = ke->spu_bottom - ke->spu_top + 1; + kb->bpp = 2; + kb->type = kate_bitmap_type_paletted; + kb->pixels = (unsigned char *) g_malloc (kb->width * kb->height); + if (G_UNLIKELY (!kb->pixels)) { + GST_WARNING_OBJECT (ke, "Failed to allocate memory for pixel data"); + return GST_FLOW_ERROR; + } + + n = 0; + pixptr[0] = GST_BUFFER_DATA (buf) + ke->spu_pix_data[0]; + pixptr[1] = GST_BUFFER_DATA (buf) + ke->spu_pix_data[1]; + nybble_offset[0] = 0; + nybble_offset[1] = 0; + max_nybbles[0] = 2 * (packet_size - ke->spu_pix_data[0]); + max_nybbles[1] = 2 * (packet_size - ke->spu_pix_data[1]); + for (y = 0; y < kb->height; ++y) { + nybble_offset[y & 1] = GST_ROUND_UP_2 (nybble_offset[y & 1]); + for (x = 0; x < kb->width;) { + if (G_UNLIKELY (nybble_offset[y & 1] >= max_nybbles[y & 1])) { + GST_DEBUG_OBJECT (ke, "RLE overflow, clearing the remainder"); + memset (kb->pixels + n, 0, kb->width - x); + n += kb->width - x; + break; + } + code = gst_kate_enc_get_rle_code (pixptr[y & 1], &nybble_offset[y & 1]); + if (code == 0) { + memset (kb->pixels + n, 0, kb->width - x); + n += kb->width - x; + break; + } else { + guint16 npixels = code >> 2; + guint16 pixel = code & 3; + if (npixels > kb->width - x) { + npixels = kb->width - x; + } + memset (kb->pixels + n, pixel, npixels); + n += npixels; + x += npixels; + } + } + } + + GST_LOG_OBJECT (ke, "%u/%u bytes left in the data packet", + max_nybbles[0] - nybble_offset[0], max_nybbles[1] - nybble_offset[1]); + + /* some streams seem to have huge uncropped SPUs, fix those up */ + x = ke->spu_left; + y = ke->spu_top; + gst_kate_enc_crop_bitmap (ke, kb, &x, &y); + + /* create the region */ + kate_region_init (kr); + if (ke->original_canvas_width > 0 && ke->original_canvas_height > 0) { + /* prefer relative sizes in case we're encoding for a different resolution + that what the SPU was created for */ + kr->metric = kate_millionths; + kr->x = 1000000 * x / ke->original_canvas_width; + kr->y = 1000000 * y / ke->original_canvas_height; + kr->w = 1000000 * kb->width / ke->original_canvas_width; + kr->h = 1000000 * kb->height / ke->original_canvas_height; + } else { + kr->metric = kate_pixel; + kr->x = x; + kr->y = y; + kr->w = kb->width; + kr->h = kb->height; + } + + /* some SPUs have no hide time */ + if (ke->hide_time == 0) { + GST_INFO_OBJECT (ke, "SPU has no hide time"); + /* now, we don't know when the next SPU is scheduled to go, since we probably + haven't received it yet, so we'll just make it a 1 second delay, which is + probably going to end before the next one while being readable */ + //ke->hide_time = ke->show_time + (1000*90/1024); + } + + return GST_FLOW_OK; +} + +#undef IGNORE +#undef ADVANCE +#undef CHECK + +static GstFlowReturn +gst_kate_enc_chain_push_packet (GstKateEnc * ke, kate_packet * kp, + GstClockTime start, GstClockTime duration) +{ + kate_int64_t granpos; + GstFlowReturn rflow; + + granpos = kate_encode_get_granule (&ke->k); + if (G_UNLIKELY (granpos < 0)) { + GST_WARNING_OBJECT (ke, "Negative granpos for packet"); + kate_packet_clear (kp); + return GST_FLOW_ERROR; + } + rflow = + gst_kate_enc_push_and_free_kate_packet (ke, kp, granpos, start, duration, + FALSE); + if (G_UNLIKELY (rflow != GST_FLOW_OK)) { + GST_WARNING_OBJECT (ke, "Failed to push Kate packet"); + } + return rflow; +} + +static void +gst_kate_enc_generate_keepalive (GstKateEnc * ke, GstClockTime timestamp) +{ + kate_packet kp; + int ret; + kate_float t = timestamp / (double) GST_SECOND; + GST_DEBUG_OBJECT (ke, "keepalive at %f", t); + ret = kate_encode_keepalive (&ke->k, t, &kp); + if (ret < 0) { + GST_WARNING_OBJECT (ke, "Failed to encode keepalive packet: %d", ret); + } else { + kate_int64_t granpos = kate_encode_get_granule (&ke->k); + GST_LOG_OBJECT (ke, "Keepalive packet encoded"); + if (gst_kate_enc_push_and_free_kate_packet (ke, &kp, granpos, timestamp, 0, + FALSE)) { + GST_WARNING_OBJECT (ke, "Failed to push keepalive packet"); + } + } +} + +static GstFlowReturn +gst_kate_enc_flush_waiting (GstKateEnc * ke, GstClockTime now) +{ + GstFlowReturn rflow = GST_FLOW_OK; + if (ke->delayed_spu) { + int ret; + kate_packet kp; + GstClockTime keepalive_time; + + kate_float t0 = ke->delayed_start / (double) GST_SECOND; + kate_float t1 = now / (double) GST_SECOND; + + GST_INFO_OBJECT (ke, + "We had a delayed SPU packet starting at %f, flushing at %f (assumed duration %f)", + t0, t1, t1 - t0); + + ret = kate_encode_text (&ke->k, t0, t1, "", 0, &kp); + if (G_UNLIKELY (ret < 0)) { + rflow = GST_FLOW_ERROR; + } else { + rflow = + gst_kate_enc_chain_push_packet (ke, &kp, ke->delayed_start, + now - ke->delayed_start + 1); + } + + if (rflow == GST_FLOW_OK) { + GST_DEBUG_OBJECT (ke, "delayed SPU packet flushed"); + } else { + GST_WARNING_OBJECT (ke, "Failed to flush delayed SPU packet: %d", rflow); + } + + /* forget it even if we couldn't flush it */ + ke->delayed_spu = FALSE; + + /* now that we've flushed the packet, we want to insert keepalives as requested */ + if (ke->keepalive_min_time > 0.0f && t1 > t0) { + GST_INFO_OBJECT (ke, "generating keepalives at %f from %f to %f", + ke->keepalive_min_time, t0, t1); + for (keepalive_time = ke->delayed_start; + (keepalive_time += ke->keepalive_min_time * GST_SECOND) < now;) { + GST_INFO_OBJECT (ke, "generating keepalive at %f", + keepalive_time / (double) GST_SECOND); + gst_kate_enc_generate_keepalive (ke, keepalive_time); + } + } + } + return rflow; +} + +static GstFlowReturn +gst_kate_enc_chain_spu (GstKateEnc * ke, GstBuffer * buf) +{ + kate_packet kp; + kate_region kregion; + kate_bitmap kbitmap; + kate_palette kpalette; + GstFlowReturn rflow; + int ret = 0; + + rflow = gst_kate_enc_decode_spu (ke, buf, &kregion, &kbitmap, &kpalette); + if (G_UNLIKELY (rflow != GST_FLOW_OK)) { + GST_ERROR_OBJECT (ke, "Failed to decode incoming SPU"); +#if 0 + { + static int spu_count = 0; + FILE *f; + char name[32]; + snprintf (name, sizeof (name), "/tmp/bad_spu_%04d", spu_count++); + name[sizeof (name) - 1] = 0; + f = fopen (name, "w"); + if (f) { + fwrite (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf), 1, f); + fclose (f); + } + } +#endif + } else if (G_UNLIKELY (kbitmap.width == 0 || kbitmap.height == 0)) { + /* there are some DVDs (well, at least one) where some dimwits put in a wholly transparent full screen 720x576 SPU !!!!?! */ + GST_WARNING_OBJECT (ke, "SPU is totally invisible - dimwits"); + rflow = GST_FLOW_OK; + } else { + /* timestamp offsets are hidden in the SPU packets */ + GstClockTime start = + GST_BUFFER_TIMESTAMP (buf) + GST_KATE_STM_TO_GST (ke->show_time); + GstClockTime stop = + GST_BUFFER_TIMESTAMP (buf) + GST_KATE_STM_TO_GST (ke->hide_time); + kate_float t0 = start / (double) GST_SECOND; + kate_float t1 = stop / (double) GST_SECOND; + GST_DEBUG_OBJECT (ke, "buf ts %f, start/show %hu/%hu", + GST_BUFFER_TIMESTAMP (buf) / (double) GST_SECOND, ke->show_time, + ke->hide_time); + +#if 0 + { + static int spu_count = 0; + FILE *f; + char name[32]; + snprintf (name, sizeof (name), "/tmp/spu_%04d", spu_count++); + name[sizeof (name) - 1] = 0; + f = fopen (name, "w"); + if (f) { + fwrite (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf), 1, f); + fclose (f); + } + } +#endif + GST_DEBUG_OBJECT (ke, "Encoding %dx%d SPU: (%u bytes) from %f to %f", + kbitmap.width, kbitmap.height, GST_BUFFER_SIZE (buf), t0, t1); + ret = kate_encode_set_region (&ke->k, &kregion); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (ke, "Failed to set event region (%d)", ret); + rflow = GST_FLOW_ERROR; + } else { + ret = kate_encode_set_palette (&ke->k, &kpalette); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (ke, "Failed to set event palette (%d)", ret); + rflow = GST_FLOW_ERROR; + } else { + ret = kate_encode_set_bitmap (&ke->k, &kbitmap); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (ke, "Failed to set event bitmap (%d)", ret); + rflow = GST_FLOW_ERROR; + } else { + /* Some SPUs have no hide time - so I'm going to delay the encoding of the packet + till either a suitable event happens, and the time of this event will be used + as the end time of this SPU, which will then be encoded and sent off. Suitable + events are the arrival of a subsequent SPU (eg, this SPU will replace the one + with no end), EOS, a new segment event, or a time threshold being reached */ + if (ke->hide_time <= ke->show_time) { + GST_INFO_OBJECT (ke, + "Cannot encode SPU packet now, hide time is now known (starting at %f) - delaying", + t0); + ke->delayed_spu = TRUE; + ke->delayed_start = start; + rflow = GST_FLOW_OK; + } else { + ret = kate_encode_text (&ke->k, t0, t1, "", 0, &kp); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (ke, + "Failed to encode empty text for SPU buffer (%d)", ret); + rflow = GST_FLOW_ERROR; + } else { + rflow = + gst_kate_enc_chain_push_packet (ke, &kp, start, + stop - start + 1); + } + } + } + } + } + g_free (kpalette.colors); + g_free (kbitmap.pixels); + } + + return rflow; +} + +static GstFlowReturn +gst_kate_enc_chain_text (GstKateEnc * ke, GstBuffer * buf, + const char *mime_type) +{ + kate_packet kp; + int ret = 0; + GstFlowReturn rflow; + GstClockTime start = GST_BUFFER_TIMESTAMP (buf); + GstClockTime stop = GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf); + + if (!strcmp (mime_type, "text/x-pango-markup")) { + ret = kate_encode_set_markup_type (&ke->k, kate_markup_simple); + } else { + ret = kate_encode_set_markup_type (&ke->k, kate_markup_none); + } + + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (ke, "Failed to set markup type (%d)", ret); + rflow = GST_FLOW_ERROR; + } else { + const char *text = (const char *) GST_BUFFER_DATA (buf); + if (text) { + size_t text_len = GST_BUFFER_SIZE (buf); + kate_float t0 = start / (double) GST_SECOND; + kate_float t1 = stop / (double) GST_SECOND; + GST_LOG_OBJECT (ke, "Encoding text: %*.*s (%u bytes) from %f to %f", + (int) text_len, (int) text_len, GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf), t0, t1); + ret = kate_encode_text (&ke->k, t0, t1, text, text_len, &kp); + if (G_UNLIKELY (ret < 0)) { + rflow = GST_FLOW_ERROR; + } else { + rflow = + gst_kate_enc_chain_push_packet (ke, &kp, start, stop - start + 1); + } + } else { + GST_WARNING_OBJECT (ke, "No text in text packet"); + rflow = GST_FLOW_ERROR; + } + } + + return rflow; +} + +/* chain function + * this function does the actual processing + */ +static GstFlowReturn +gst_kate_enc_chain (GstPad * pad, GstBuffer * buf) +{ + GstKateEnc *ke = GST_KATE_ENC (gst_pad_get_parent (pad)); + GstFlowReturn rflow = GST_FLOW_OK; + GstCaps *caps; + const gchar *mime_type = NULL; + + GST_DEBUG_OBJECT (ke, "got packet, %u bytes", GST_BUFFER_SIZE (buf)); + + /* get the type of the data we're being sent */ + caps = GST_PAD_CAPS (pad); + if (G_UNLIKELY (caps == NULL)) { + GST_ERROR_OBJECT (ke, ": Could not get caps of pad"); + rflow = GST_FLOW_ERROR; + } else { + const GstStructure *structure = gst_caps_get_structure (caps, 0); + if (structure) + mime_type = gst_structure_get_name (structure); + + if (mime_type) { + GST_LOG_OBJECT (ke, "Packet has MIME type %s", mime_type); + + /* first push headers if we haven't done that yet */ + rflow = gst_kate_enc_flush_headers (ke); + + if (G_LIKELY (rflow == GST_FLOW_OK)) { + /* flush any packet we had waiting */ + rflow = gst_kate_enc_flush_waiting (ke, GST_BUFFER_TIMESTAMP (buf)); + + if (G_LIKELY (rflow == GST_FLOW_OK)) { + if (!strcmp (mime_type, GST_KATE_SPU_MIME_TYPE)) { + /* encode a kate_bitmap */ + rflow = gst_kate_enc_chain_spu (ke, buf); + } else { + /* encode text */ + rflow = gst_kate_enc_chain_text (ke, buf, mime_type); + } + } + } + } else { + GST_WARNING_OBJECT (ke, "Packet has no MIME type, ignored"); + } + } + + gst_buffer_unref (buf); + + gst_object_unref (ke); + + GST_LOG_OBJECT (ke, "Leaving chain function"); + + return rflow; +} + +static GstStateChangeReturn +gst_kate_enc_change_state (GstElement * element, GstStateChange transition) +{ + GstKateEnc *ke = GST_KATE_ENC (element); + GstStateChangeReturn res; + int ret; + + GST_INFO_OBJECT (ke, "gst_kate_enc_change_state"); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + ke->tags = gst_tag_list_new (); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG_OBJECT (ke, "READY -> PAUSED, initializing kate state"); + ret = kate_info_init (&ke->ki); + if (ret < 0) { + GST_WARNING_OBJECT (ke, "failed to initialize kate info structure: %d", + ret); + break; + } + if (ke->language) { + ret = kate_info_set_language (&ke->ki, ke->language); + if (ret < 0) { + GST_WARNING_OBJECT (ke, "failed to set stream language: %d", ret); + break; + } + } + if (ke->category) { + ret = kate_info_set_category (&ke->ki, ke->category); + if (ret < 0) { + GST_WARNING_OBJECT (ke, "failed to set stream category: %d", ret); + break; + } + } + ret = + kate_info_set_original_canvas_size (&ke->ki, + ke->original_canvas_width, ke->original_canvas_height); + if (ret < 0) { + GST_WARNING_OBJECT (ke, "failed to set original canvas size: %d", ret); + break; + } + ret = kate_comment_init (&ke->kc); + if (ret < 0) { + GST_WARNING_OBJECT (ke, + "failed to initialize kate comment structure: %d", ret); + break; + } + ret = kate_encode_init (&ke->k, &ke->ki); + if (ret < 0) { + GST_WARNING_OBJECT (ke, "failed to initialize kate state: %d", ret); + break; + } + ke->headers_sent = FALSE; + ke->initialized = TRUE; + ke->last_timestamp = 0; + ke->latest_end_time = 0; + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_tag_list_free (ke->tags); + ke->tags = NULL; + break; + default: + break; + } + + res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (res == GST_STATE_CHANGE_FAILURE) { + GST_WARNING_OBJECT (ke, "Parent failed to change state"); + return res; + } + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT (ke, "PAUSED -> READY, clearing kate state"); + if (ke->initialized) { + kate_clear (&ke->k); + kate_info_clear (&ke->ki); + kate_comment_clear (&ke->kc); + ke->initialized = FALSE; + ke->last_timestamp = 0; + ke->latest_end_time = 0; + } + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + GST_DEBUG_OBJECT (ke, "State change done"); + + return res; +} + +static GstClockTime +gst_kate_enc_granule_time (kate_state * k, gint64 granulepos) +{ + float t; + + if (granulepos == -1) + return -1; + + t = kate_granule_time (k->ki, granulepos); + return t * GST_SECOND; +} + +/* +conversions on the sink: + - nothing +conversions on the source: + - default is granules at num/den rate + - default -> time is possible + - bytes do not mean anything, packets can be any number of bytes, and we + have no way to know the number of bytes emitted without decoding +*/ + +static gboolean +gst_kate_enc_convert (GstPad * pad, GstFormat src_fmt, gint64 src_val, + GstFormat * dest_fmt, gint64 * dest_val) +{ + GstKateEnc *ke; + gboolean res = FALSE; + + if (src_fmt == *dest_fmt) { + *dest_val = src_val; + return TRUE; + } + + ke = GST_KATE_ENC (gst_pad_get_parent (pad)); + + if (!ke->initialized) { + GST_WARNING_OBJECT (ke, "not initialized yet"); + gst_object_unref (ke); + return FALSE; + } + + if (src_fmt == GST_FORMAT_BYTES || *dest_fmt == GST_FORMAT_BYTES) { + GST_WARNING_OBJECT (ke, "unsupported format"); + gst_object_unref (ke); + return FALSE; + } + + switch (src_fmt) { + case GST_FORMAT_DEFAULT: + switch (*dest_fmt) { + case GST_FORMAT_TIME: + *dest_val = gst_kate_enc_granule_time (&ke->k, src_val); + res = TRUE; + break; + default: + res = FALSE; + break; + } + break; + default: + res = FALSE; + break; + } + + if (!res) { + GST_WARNING_OBJECT (ke, "unsupported format"); + } + + gst_object_unref (ke); + return res; +} + +#if 1 +static const GstQueryType * +gst_kate_enc_source_query_type (GstPad * pad) +{ + static const GstQueryType types[] = { + GST_QUERY_CONVERT, + 0 + }; + + return types; +} +#endif + +static gboolean +gst_kate_enc_source_query (GstPad * pad, GstQuery * query) +{ + GstKateEnc *ke; + gboolean res = FALSE; + + ke = GST_KATE_ENC (gst_pad_get_parent (pad)); + + GST_DEBUG ("source query %d", GST_QUERY_TYPE (query)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + if (!gst_kate_enc_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val)) { + return gst_pad_query_default (pad, query); + } + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + res = TRUE; + } + break; + default: + res = gst_pad_query_default (pad, query); + break; + } + + gst_object_unref (ke); + + return res; +} + +static gboolean +gst_kate_enc_sink_event (GstPad * pad, GstEvent * event) +{ + GstKateEnc *ke = GST_KATE_ENC (gst_pad_get_parent (pad)); + GstStructure *structure; + gboolean ret; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + GST_LOG_OBJECT (ke, "Got newsegment event"); + if (ke->initialized) { + GST_LOG_OBJECT (ke, "ensuring all headers are in"); + if (gst_kate_enc_flush_headers (ke) != GST_FLOW_OK) { + GST_WARNING_OBJECT (ke, "Failed to flush headers"); + } else { + GstFormat format; + gint64 timestamp; + + gst_event_parse_new_segment (event, NULL, NULL, &format, ×tamp, + NULL, NULL); + if (format != GST_FORMAT_TIME || !GST_CLOCK_TIME_IS_VALID (timestamp)) { + GST_WARNING_OBJECT (ke, + "No time in newsegment event %p, format %d, timestamp %lld", + event, (int) format, (long long) timestamp); + /* to be safe, we'd need to generate a keepalive anyway, but we'd have to guess at the timestamp to use; a + good guess would be the last known timestamp plus the keepalive time, but if we then get a packet with a + timestamp less than this, it would fail to encode, which would be Bad. If we don't encode a keepalive, we + run the risk of stalling the pipeline and hanging, which is Very Bad. Oh dear. We can't exit(-1), can we ? */ + } else { + float t = timestamp / (double) GST_SECOND; + + if (ke->delayed_spu + && t - ke->delayed_start / (double) GST_SECOND >= + ke->default_spu_duration) { + if (G_UNLIKELY (gst_kate_enc_flush_waiting (ke, + timestamp) != GST_FLOW_OK)) { + GST_WARNING_OBJECT (ke, "Failed to encode delayed packet"); + /* continue with new segment handling anyway */ + } + } + + GST_LOG_OBJECT (ke, "ts %f, last %f (min %f)", t, + ke->last_timestamp / (double) GST_SECOND, + ke->keepalive_min_time); + if (ke->keepalive_min_time > 0.0f + && t - ke->last_timestamp / (double) GST_SECOND >= + ke->keepalive_min_time) { + /* we only generate a keepalive if there is no SPU waiting, as it would + mean out of sequence start times - and granulepos */ + if (!ke->delayed_spu) { + gst_kate_enc_generate_keepalive (ke, timestamp); + } + } + } + } + } + ret = gst_pad_push_event (ke->srcpad, event); + break; + + case GST_EVENT_CUSTOM_DOWNSTREAM: + GST_LOG_OBJECT (ke, "Got custom downstream event"); + /* adapted from the dvdsubdec element */ + structure = event->structure; + if (structure != NULL + && gst_structure_has_name (structure, "application/x-gst-dvd")) { + if (ke->initialized) { + GST_LOG_OBJECT (ke, "ensuring all headers are in"); + if (gst_kate_enc_flush_headers (ke) != GST_FLOW_OK) { + GST_WARNING_OBJECT (ke, "Failed to flush headers"); + } else { + const gchar *event_name = + gst_structure_get_string (structure, "event"); + if (event_name) { + if (!strcmp (event_name, "dvd-spu-clut-change")) { + gchar name[16]; + int idx; + gboolean found; + gint value; + GST_INFO_OBJECT (ke, "New CLUT received"); + for (idx = 0; idx < 16; ++idx) { + g_snprintf (name, sizeof (name), "clut%02d", idx); + found = gst_structure_get_int (structure, name, &value); + if (found) { + ke->spu_clut[idx] = value; + } else { + GST_WARNING_OBJECT (ke, + "DVD CLUT event did not contain %s field", name); + } + } + } else if (!strcmp (event_name, "dvd-lang-codes")) { + /* we can't know which stream corresponds to us */ + } + } else { + GST_WARNING_OBJECT (ke, "custom downstream event with no name"); + } + } + } + } + ret = gst_pad_push_event (ke->srcpad, event); + break; + + case GST_EVENT_TAG: + GST_LOG_OBJECT (ke, "Got tag event"); + if (ke->tags) { + GstTagList *list; + + gst_event_parse_tag (event, &list); + gst_tag_list_insert (ke->tags, list, + gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (ke))); + } else { + g_assert_not_reached (); + } + ret = gst_pad_event_default (pad, event); + break; + + case GST_EVENT_EOS: + GST_INFO_OBJECT (ke, "Got EOS event"); + if (ke->initialized) { + GST_LOG_OBJECT (ke, "ensuring all headers are in"); + if (gst_kate_enc_flush_headers (ke) != GST_FLOW_OK) { + GST_WARNING_OBJECT (ke, "Failed to flush headers"); + } else { + kate_packet kp; + int ret; + GstClockTime delayed_end = + ke->delayed_start + ke->default_spu_duration * GST_SECOND; + + if (G_UNLIKELY (gst_kate_enc_flush_waiting (ke, + delayed_end) != GST_FLOW_OK)) { + GST_WARNING_OBJECT (ke, "Failed to encode delayed packet"); + /* continue with EOS handling anyway */ + } + + ret = kate_encode_finish (&ke->k, -1, &kp); + if (ret < 0) { + GST_WARNING_OBJECT (ke, "Failed to encode EOS packet: %d", ret); + } else { + kate_int64_t granpos = kate_encode_get_granule (&ke->k); + GST_LOG_OBJECT (ke, "EOS packet encoded"); + if (gst_kate_enc_push_and_free_kate_packet (ke, &kp, granpos, + ke->latest_end_time, 0, FALSE)) { + GST_WARNING_OBJECT (ke, "Failed to push EOS packet"); + } + } + } + } + ret = gst_pad_event_default (pad, event); + break; + + default: + GST_LOG_OBJECT (ke, "Got unhandled event"); + ret = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (ke); + return ret; +} diff --git a/ext/kate/gstkateenc.h b/ext/kate/gstkateenc.h new file mode 100644 index 0000000000..168de711f6 --- /dev/null +++ b/ext/kate/gstkateenc.h @@ -0,0 +1,120 @@ +/* + * GStreamer + * Copyright 2005 Thomas Vander Stichele + * Copyright 2005 Ronald S. Bultje + * Copyright 2008 Vincent Penquerc'h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_KATE_ENC_H__ +#define __GST_KATE_ENC_H__ + +#include +#include + +G_BEGIN_DECLS +/* #defines don't like whitespacey bits */ +#define GST_TYPE_KATE_ENC \ + (gst_kate_enc_get_type()) +#define GST_KATE_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_KATE_ENC,GstKateEnc)) +#define GST_KATE_ENC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_KATE,GstKateEncClass)) +#define GST_IS_KATE_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_KATE_ENC)) +#define GST_IS_KATE_ENC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_KATE_ENC)) +typedef struct _GstKateEnc GstKateEnc; +typedef struct _GstKateEncClass GstKateEncClass; + +struct _GstKateEnc +{ + GstElement element; + + GstPad *sinkpad, *srcpad; + + kate_info ki; + kate_comment kc; + kate_state k; + + GstTagList *tags; + + GstClockTime last_timestamp; + GstClockTime latest_end_time; + + gboolean headers_sent; + gboolean initialized; + gboolean delayed_spu; + GstClockTime delayed_start; + gchar *language; + gchar *category; + + int granule_rate_numerator; + int granule_rate_denominator; + int granule_shift; + + float keepalive_min_time; + float default_spu_duration; + + size_t original_canvas_width; + size_t original_canvas_height; + + /* SPU decoding */ + guint8 spu_colormap[4]; + guint32 spu_clut[16]; + guint8 spu_alpha[4]; + guint16 spu_top; + guint16 spu_left; + guint16 spu_right; + guint16 spu_bottom; + guint16 spu_pix_data[2]; + guint16 show_time; + guint16 hide_time; +}; + +struct _GstKateEncClass +{ + GstElementClass parent_class; +}; + +GType gst_kate_enc_get_type (void); + +G_END_DECLS +#endif /* __GST_KATE_ENC_H__ */ diff --git a/ext/kate/gstkateparse.c b/ext/kate/gstkateparse.c new file mode 100644 index 0000000000..84a752b7a7 --- /dev/null +++ b/ext/kate/gstkateparse.c @@ -0,0 +1,613 @@ +/* GStreamer + * Copyright (C) <2004> Thomas Vander Stichele + * Copyright (C) 2006 Andy Wingo + * Copyright (C) 2008 Vincent Penquerc'h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-kateparse + * @short_description: parses kate streams + * @see_also: katedec, vorbisparse, oggdemux, theoraparse + * + * + * + * The kateparse element will parse the header packets of the Kate + * stream and put them as the streamheader in the caps. This is used in the + * multifdsink case where you want to stream live kate streams to multiple + * clients, each client has to receive the streamheaders first before they can + * consume the kate packets. + * + * + * This element also makes sure that the buffers that it pushes out are properly + * timestamped and that their offset and offset_end are set. The buffers that + * kateparse outputs have all of the metadata that oggmux expects to receive, + * which allows you to (for example) remux an ogg/kate file. + * + * Example pipelines + * + * + * gst-launch -v filesrc location=kate.ogg ! oggdemux ! kateparse ! fakesink + * + * This pipeline shows that the streamheader is set in the caps, and that each + * buffer has the timestamp, duration, offset, and offset_end set. + * + * + * + * gst-launch filesrc location=kate.ogg ! oggdemux ! kateparse \ + * ! oggmux ! filesink location=kate-remuxed.ogg + * + * This pipeline shows remuxing. kate-remuxed.ogg might not be exactly the same + * as kate.ogg, but they should produce exactly the same decoded data. + * + * + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstkate.h" +#include "gstkateutil.h" +#include "gstkateparse.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_kateparse_debug); +#define GST_CAT_DEFAULT gst_kateparse_debug + +static const GstElementDetails gst_kate_parse_details = +GST_ELEMENT_DETAILS ("Kate stream parser", + "Codec/Parser/Subtitle", + "parse raw kate streams", + "Vincent Penquerc'h "); + +static GstStaticPadTemplate gst_kate_parse_sink_factory = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_KATE_MIME_TYPE) + ); + +static GstStaticPadTemplate gst_kate_parse_src_factory = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_KATE_MIME_TYPE) + ); + +GST_BOILERPLATE (GstKateParse, gst_kate_parse, GstElement, GST_TYPE_ELEMENT); + +static GstFlowReturn gst_kate_parse_chain (GstPad * pad, GstBuffer * buffer); +static GstStateChangeReturn gst_kate_parse_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_kate_parse_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_kate_parse_src_query (GstPad * pad, GstQuery * query); +#if 0 +static gboolean gst_kate_parse_convert (GstPad * pad, + GstFormat src_format, gint64 src_value, + GstFormat * dest_format, gint64 * dest_value); +#endif +static GstFlowReturn gst_kate_parse_parse_packet (GstKateParse * parse, + GstBuffer * buf); + +static void +gst_kate_parse_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_kate_parse_src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_kate_parse_sink_factory)); + gst_element_class_set_details (element_class, &gst_kate_parse_details); +} + +static void +gst_kate_parse_class_init (GstKateParseClass * klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + gstelement_class->change_state = gst_kate_parse_change_state; + + klass->parse_packet = GST_DEBUG_FUNCPTR (gst_kate_parse_parse_packet); +} + +static void +gst_kate_parse_init (GstKateParse * parse, GstKateParseClass * g_class) +{ + parse->sinkpad = + gst_pad_new_from_static_template (&gst_kate_parse_sink_factory, "sink"); + gst_pad_set_chain_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_kate_parse_chain)); + gst_pad_set_event_function (parse->sinkpad, + GST_DEBUG_FUNCPTR (gst_kate_parse_sink_event)); + gst_element_add_pad (GST_ELEMENT (parse), parse->sinkpad); + + parse->srcpad = + gst_pad_new_from_static_template (&gst_kate_parse_src_factory, "src"); + gst_pad_set_query_function (parse->srcpad, + GST_DEBUG_FUNCPTR (gst_kate_parse_src_query)); + gst_element_add_pad (GST_ELEMENT (parse), parse->srcpad); +} + +static void +gst_kate_parse_drain_event_queue (GstKateParse * parse) +{ + while (parse->event_queue->length) { + GstEvent *event; + + event = GST_EVENT_CAST (g_queue_pop_head (parse->event_queue)); + gst_pad_event_default (parse->sinkpad, event); + } +} + +static GstFlowReturn +gst_kate_parse_push_headers (GstKateParse * parse) +{ + /* mark and put on caps */ + GstCaps *caps; + GstBuffer *outbuf; + kate_packet packet; + GList *headers, *outbuf_list = NULL; + int ret; + gboolean res; + + /* get the headers into the caps, passing them to kate as we go */ + caps = + gst_kate_util_set_header_on_caps (&parse->element, + gst_pad_get_caps (parse->srcpad), parse->streamheader); + if (G_UNLIKELY (!caps)) { + GST_ERROR_OBJECT (parse, "Failed to set headers on caps"); + return GST_FLOW_ERROR; + } + + GST_DEBUG_OBJECT (parse, "here are the caps: %" GST_PTR_FORMAT, caps); + res = gst_pad_set_caps (parse->srcpad, caps); + gst_caps_unref (caps); + if (G_UNLIKELY (!res)) { + GST_WARNING_OBJECT (parse, "Failed to set pad caps"); + return GST_FLOW_ERROR; + } + + headers = parse->streamheader; + while (headers) { + outbuf = GST_BUFFER_CAST (headers->data); + kate_packet_wrap (&packet, GST_BUFFER_SIZE (outbuf), + GST_BUFFER_DATA (outbuf)); + ret = kate_decode_headerin (&parse->ki, &parse->kc, &packet); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (parse, "kate_decode_headerin returned %d", ret); + } + outbuf_list = g_list_append (outbuf_list, outbuf); + headers = headers->next; + } + + /* first process queued events */ + gst_kate_parse_drain_event_queue (parse); + + /* push out buffers, ignoring return value... */ + headers = outbuf_list; + while (headers) { + outbuf = GST_BUFFER_CAST (headers->data); + gst_buffer_set_caps (outbuf, GST_PAD_CAPS (parse->srcpad)); + gst_pad_push (parse->srcpad, outbuf); + headers = headers->next; + } + + g_list_free (outbuf_list); + + parse->streamheader_sent = TRUE; + + return GST_FLOW_OK; +} + +static void +gst_kate_parse_clear_queue (GstKateParse * parse) +{ + GST_DEBUG_OBJECT (parse, "Clearing queue"); + while (parse->buffer_queue->length) { + GstBuffer *buf; + + buf = GST_BUFFER_CAST (g_queue_pop_head (parse->buffer_queue)); + gst_buffer_unref (buf); + } + while (parse->event_queue->length) { + GstEvent *event; + + event = GST_EVENT_CAST (g_queue_pop_head (parse->event_queue)); + gst_event_unref (event); + } +} + +static GstFlowReturn +gst_kate_parse_push_buffer (GstKateParse * parse, GstBuffer * buf, + gint64 granulepos) +{ + GST_LOG_OBJECT (parse, "granulepos %16llx", granulepos); + if (granulepos < 0) { + /* packets coming not from Ogg won't have a granpos in the offset end, + so we have to synthesize one here - only problem is we don't know + the backlink - pretend there's none for now */ + GST_INFO_OBJECT (parse, "No granulepos on buffer, synthesizing one"); + granulepos = + kate_duration_granule (&parse->ki, + GST_BUFFER_TIMESTAMP (buf) / + (double) GST_SECOND) << kate_granule_shift (&parse->ki); + } + GST_BUFFER_OFFSET (buf) = + kate_granule_time (&parse->ki, granulepos) * GST_SECOND; + GST_BUFFER_OFFSET_END (buf) = granulepos; + GST_BUFFER_TIMESTAMP (buf) = GST_BUFFER_OFFSET (buf); + + /* Hack to flush each packet on its own page - taken off the CMML encoder element */ + /* TODO: this is shite and needs to go once I find a way to tell Ogg to flush + as it messes up Matroska's track duration */ + GST_BUFFER_DURATION (buf) = G_MAXINT64; + + gst_buffer_set_caps (buf, GST_PAD_CAPS (parse->srcpad)); + + return gst_pad_push (parse->srcpad, buf); +} + +static GstFlowReturn +gst_kate_parse_drain_queue_prematurely (GstKateParse * parse) +{ + GstFlowReturn ret = GST_FLOW_OK; + + /* got an EOS event, make sure to push out any buffers that were in the queue + * -- won't normally be the case, but this catches the + * didn't-get-a-granulepos-on-the-last-packet case. Assuming a continuous + * stream. */ + + /* if we got EOS before any buffers came, go ahead and push the other events + * first */ + gst_kate_parse_drain_event_queue (parse); + + while (!g_queue_is_empty (parse->buffer_queue)) { + GstBuffer *buf; + gint64 granpos; + + buf = GST_BUFFER_CAST (g_queue_pop_head (parse->buffer_queue)); + + granpos = GST_BUFFER_OFFSET_END (buf); + ret = gst_kate_parse_push_buffer (parse, buf, granpos); + + if (ret != GST_FLOW_OK) + goto done; + } + + g_assert (g_queue_is_empty (parse->buffer_queue)); + +done: + return ret; +} + +static GstFlowReturn +gst_kate_parse_drain_queue (GstKateParse * parse, gint64 granulepos) +{ + GstFlowReturn ret = GST_FLOW_OK; + + if (!g_queue_is_empty (parse->buffer_queue)) { + GstBuffer *buf; + buf = GST_BUFFER_CAST (g_queue_pop_head (parse->buffer_queue)); + ret = gst_kate_parse_push_buffer (parse, buf, granulepos); + + if (ret != GST_FLOW_OK) + goto done; + } + g_assert (g_queue_is_empty (parse->buffer_queue)); + +done: + return ret; +} + +static GstFlowReturn +gst_kate_parse_queue_buffer (GstKateParse * parse, GstBuffer * buf) +{ + GstFlowReturn ret = GST_FLOW_OK; + gint64 granpos; + + buf = gst_buffer_make_metadata_writable (buf); + + /* oggdemux stores the granule pos in the offset end */ + granpos = GST_BUFFER_OFFSET_END (buf); + GST_LOG_OBJECT (parse, "granpos %16llx", granpos); + g_queue_push_tail (parse->buffer_queue, buf); + +#if 1 + /* if getting buffers from matroska, we won't have a granpos here... */ + //if (GST_BUFFER_OFFSET_END_IS_VALID (buf)) { + ret = gst_kate_parse_drain_queue (parse, granpos); + //} +#else + if (granpos >= 0) { + ret = gst_kate_parse_drain_queue (parse, granpos); + } else { + GST_WARNING_OBJECT (parse, "granulepos < 0 (%lld)", granpos); + ret = GST_FLOW_ERROR; + } +#endif + + return ret; +} + +static GstFlowReturn +gst_kate_parse_parse_packet (GstKateParse * parse, GstBuffer * buf) +{ + GstFlowReturn ret = GST_FLOW_OK; + + g_assert (parse); + + parse->packetno++; + + GST_LOG_OBJECT (parse, "Got packet %02x, %u bytes", + GST_BUFFER_SIZE (buf) ? GST_BUFFER_DATA (buf)[0] : -1, + GST_BUFFER_SIZE (buf)); + + if (GST_BUFFER_SIZE (buf) > 0 && GST_BUFFER_DATA (buf)[0] & 0x80) { + GST_DEBUG_OBJECT (parse, "Found header %02x", GST_BUFFER_DATA (buf)[0]); + /* if 0x80 is set, it's streamheader, + * so put it on the streamheader list and return */ + parse->streamheader = g_list_append (parse->streamheader, buf); + ret = GST_FLOW_OK; + } else { + if (!parse->streamheader_sent) { + GST_DEBUG_OBJECT (parse, "Found non header, pushing headers seen so far"); + ret = gst_kate_parse_push_headers (parse); + } + + if (ret == GST_FLOW_OK) { + ret = gst_kate_parse_queue_buffer (parse, buf); + } + } + + return ret; +} + +static GstFlowReturn +gst_kate_parse_chain (GstPad * pad, GstBuffer * buffer) +{ + GstKateParseClass *klass; + GstKateParse *parse; + + parse = GST_KATE_PARSE (GST_PAD_PARENT (pad)); + klass = GST_KATE_PARSE_CLASS (G_OBJECT_GET_CLASS (parse)); + + g_assert (klass->parse_packet != NULL); + + return klass->parse_packet (parse, buffer); +} + +static gboolean +gst_kate_parse_queue_event (GstKateParse * parse, GstEvent * event) +{ + GstFlowReturn ret = TRUE; + + g_queue_push_tail (parse->event_queue, event); + + return ret; +} + +static gboolean +gst_kate_parse_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean ret; + GstKateParse *parse; + + parse = GST_KATE_PARSE (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + gst_kate_parse_clear_queue (parse); + ret = gst_pad_event_default (pad, event); + break; + case GST_EVENT_EOS: + if (!parse->streamheader_sent) { + GST_DEBUG_OBJECT (parse, "Got EOS, pushing headers seen so far"); + ret = gst_kate_parse_push_headers (parse); + if (ret != GST_FLOW_OK) + goto done; + } + gst_kate_parse_drain_queue_prematurely (parse); + ret = gst_pad_event_default (pad, event); + break; + default: + if (!parse->streamheader_sent && GST_EVENT_IS_SERIALIZED (event)) + ret = gst_kate_parse_queue_event (parse, event); + else + ret = gst_pad_event_default (pad, event); + break; + } + +done: + gst_object_unref (parse); + + return ret; +} + +#if 0 +static gboolean +gst_kate_parse_convert (GstPad * pad, + GstFormat src_format, gint64 src_value, + GstFormat * dest_format, gint64 * dest_value) +{ + gboolean res = TRUE; + GstKateParse *parse; + + parse = GST_KATE_PARSE (GST_PAD_PARENT (pad)); + + /* fixme: assumes atomic access to lots of instance variables modified from + * the streaming thread, including 64-bit variables */ + + if (!parse->streamheader_sent) + return FALSE; + + if (src_format == *dest_format) { + *dest_value = src_value; + return TRUE; + } + + if (parse->sinkpad == pad && + (src_format == GST_FORMAT_BYTES || *dest_format == GST_FORMAT_BYTES)) + return FALSE; + + switch (src_format) { + case GST_FORMAT_TIME: + switch (*dest_format) { + default: + res = FALSE; + } + break; + case GST_FORMAT_DEFAULT: + switch (*dest_format) { + case GST_FORMAT_TIME: + *dest_value = kate_granule_time (&parse->ki, src_value) * GST_SECOND; + break; + default: + res = FALSE; + } + break; + default: + res = FALSE; + } + + return res; +} +#endif + +static gboolean +gst_kate_parse_src_query (GstPad * pad, GstQuery * query) +{ +#if 1 + // TODO + GST_WARNING ("gst_kate_parse_src_query"); + return FALSE; +#else + gint64 granulepos; + GstKateParse *parse; + gboolean res = FALSE; + + parse = GST_KATE_PARSE (GST_PAD_PARENT (pad)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + GstFormat format; + gint64 value; + + granulepos = parse->prev_granulepos; + + gst_query_parse_position (query, &format, NULL); + + /* and convert to the final format */ + if (!(res = + gst_kate_parse_convert (pad, GST_FORMAT_DEFAULT, granulepos, + &format, &value))) + goto error; + + /* fixme: support segments + value = (value - parse->segment_start) + parse->segment_time; + */ + + gst_query_set_position (query, format, value); + + GST_LOG_OBJECT (parse, "query %p: peer returned granulepos: %" + G_GUINT64_FORMAT " - we return %" G_GUINT64_FORMAT " (format %u)", + query, granulepos, value, format); + + break; + } + case GST_QUERY_DURATION: + { + /* fixme: not threadsafe */ + /* query peer for total length */ + if (!gst_pad_is_linked (parse->sinkpad)) { + GST_WARNING_OBJECT (parse, "sink pad %" GST_PTR_FORMAT " is not linked", + parse->sinkpad); + goto error; + } + if (!(res = gst_pad_query (GST_PAD_PEER (parse->sinkpad), query))) + goto error; + break; + } + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + if (!(res = + gst_kate_parse_convert (pad, src_fmt, src_val, &dest_fmt, + &dest_val))) + goto error; + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + return res; + +error: + { + GST_WARNING_OBJECT (parse, "error handling query"); + return res; + } +#endif +} + +static GstStateChangeReturn +gst_kate_parse_change_state (GstElement * element, GstStateChange transition) +{ + GstKateParse *parse = GST_KATE_PARSE (element); + GstStateChangeReturn ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + kate_info_init (&parse->ki); + kate_comment_init (&parse->kc); + parse->packetno = 0; + parse->streamheader_sent = FALSE; + parse->streamheader = NULL; + parse->buffer_queue = g_queue_new (); + parse->event_queue = g_queue_new (); + break; + default: + break; + } + + ret = parent_class->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + kate_info_clear (&parse->ki); + kate_comment_clear (&parse->kc); + + gst_kate_parse_clear_queue (parse); + g_queue_free (parse->buffer_queue); + parse->buffer_queue = NULL; + g_queue_free (parse->event_queue); + parse->event_queue = NULL; + break; + + default: + break; + } + + return ret; +} diff --git a/ext/kate/gstkateparse.h b/ext/kate/gstkateparse.h new file mode 100644 index 0000000000..509f6c4988 --- /dev/null +++ b/ext/kate/gstkateparse.h @@ -0,0 +1,78 @@ +/* -*- c-basic-offset: 2 -*- + * GStreamer + * Copyright (C) <2004> Thomas Vander Stichele + * Copyright (C) <2008> Vincent Penquerc'h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_KATE_PARSE_H__ +#define __GST_KATE_PARSE_H__ + + +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_KATE_PARSE \ + (gst_kate_parse_get_type()) +#define GST_KATE_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_KATE_PARSE,GstKateParse)) +#define GST_KATE_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_KATE_PARSE,GstKateParseClass)) +#define GST_IS_KATE_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_KATE_PARSE)) +#define GST_IS_KATKATEE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_KATE_PARSE)) +typedef struct _GstKateParse GstKateParse; +typedef struct _GstKateParseClass GstKateParseClass; + +/** + * GstKateParse: + * + * Opaque data structure. + */ +struct _GstKateParse +{ + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + + guint packetno; + gboolean streamheader_sent; + GList *streamheader; + + GQueue *event_queue; + GQueue *buffer_queue; + + kate_info ki; + kate_comment kc; +}; + +struct _GstKateParseClass +{ + GstElementClass parent_class; + + /* virtual functions */ + GstFlowReturn (*parse_packet) (GstKateParse * parse, GstBuffer * buf); +}; + +GType gst_kate_parse_get_type (void); + +G_END_DECLS +#endif /* __GST_KATE_PARSE_H__ */ diff --git a/ext/kate/gstkatetag.c b/ext/kate/gstkatetag.c new file mode 100644 index 0000000000..608f0747fc --- /dev/null +++ b/ext/kate/gstkatetag.c @@ -0,0 +1,347 @@ +/* + * GStreamer + * Copyright (C) 2006 James Livingston + * Copyright (C) 2008 Vincent Penquerc'h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-katetag + * @see_also: #oggdemux, #oggmux, #kateparse, #GstTagSetter + * @short_description: retags kate streams + * + * + * + * The katetag element can change the tag contained within a raw + * kate stream. Specifically, it modifies the comments header packet + * of the kate stream, as well as the language and category of the + * kate stream. + * + * + * The element will also process the stream as the #kateparse element does + * so it can be used when remuxing an Ogg Kate stream, without additional + * elements. + * + * + * Applications can set the tags to write using the #GstTagSetter interface. + * Tags contained within the kate stream will be picked up + * automatically (and merged according to the merge mode set via the tag + * setter interface). + * + * Example pipelines + * + * This element is only useful with gst-launch for modifying the language + * and/or category (which are properties of the stream located in the kate + * beginning of stream header), because it does not support setting the tags + * on a #GstTagSetter interface. Conceptually, the element will usually be + * used like: + * + * gst-launch -v filesrc location=foo.ogg ! oggdemux ! katetag ! oggmux ! filesink location=bar.ogg + * + * + * + * This pipeline will set the language and category of the stream to the + * given values: + * + * gst-launch -v filesrc location=foo.ogg ! oggdemux ! katetag language=pt_BR category=subtitles ! oggmux ! filesink location=bar.ogg + * + * + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#include + +#include "gstkatetag.h" + + +GST_DEBUG_CATEGORY_EXTERN (gst_katetag_debug); +#define GST_CAT_DEFAULT gst_katetag_debug + +enum +{ + ARG_0, + ARG_LANGUAGE, + ARG_CATEGORY, + ARG_ORIGINAL_CANVAS_WIDTH, + ARG_ORIGINAL_CANVAS_HEIGHT, +}; + +static void gst_kate_tag_base_init (gpointer g_class); +static void gst_kate_tag_class_init (GstKateTagClass * klass); +static void gst_kate_tag_init (GstKateTag * kt, GstKateTagClass * g_class); +static GstFlowReturn gst_kate_tag_parse_packet (GstKateParse * parse, + GstBuffer * buffer); +static void gst_kate_tag_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_kate_tag_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_kate_tag_dispose (GObject * object); + + +#define _do_init(type) \ + G_STMT_START{ \ + static const GInterfaceInfo tag_setter_info = { \ + NULL, \ + NULL, \ + NULL \ + }; \ + g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, \ + &tag_setter_info); \ + }G_STMT_END + +GST_BOILERPLATE_FULL (GstKateTag, gst_kate_tag, GstKateParse, + GST_TYPE_KATE_PARSE, _do_init); + +static GstElementDetails kate_tag_details = +GST_ELEMENT_DETAILS ("Kate stream tagger", + "Formatter/Metadata", + "Retags kate streams", + "Vincent Penquerc'h "); + + +static void +gst_kate_tag_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &kate_tag_details); +} + +static void +gst_kate_tag_class_init (GstKateTagClass * klass) +{ + GObjectClass *gobject_class; + GstKateParseClass *gstkateparse_class; + + gobject_class = (GObjectClass *) klass; + gstkateparse_class = GST_KATE_PARSE_CLASS (klass); + + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_kate_tag_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_kate_tag_get_property); + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_kate_tag_dispose); + + g_object_class_install_property (gobject_class, ARG_LANGUAGE, + g_param_spec_string ("language", "Language", + "Set the language of the stream", "", G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_CATEGORY, + g_param_spec_string ("category", "Category", + "Set the category of the stream", "", G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_ORIGINAL_CANVAS_WIDTH, + g_param_spec_int ("original-canvas-width", "Original canvas width", + "Set the width of the canvas this stream was authored for (0 is unspecified)", + 0, G_MAXINT, 0, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_ORIGINAL_CANVAS_HEIGHT, + g_param_spec_int ("original-canvas-height", "Original canvas height", + "Set the height of the canvas this stream was authored for (0 is unspecified)", + 0, G_MAXINT, 0, G_PARAM_READWRITE)); + + gstkateparse_class->parse_packet = + GST_DEBUG_FUNCPTR (gst_kate_tag_parse_packet); +} + +static void +gst_kate_tag_init (GstKateTag * kt, GstKateTagClass * g_class) +{ + kt->language = NULL; + kt->category = NULL; + kt->original_canvas_width = -1; + kt->original_canvas_height = -1; +} + +static void +gst_kate_tag_dispose (GObject * object) +{ + GstKateTag *kt = GST_KATE_TAG (object); + + GST_LOG_OBJECT (kt, "disposing"); + + if (kt->language) { + g_free (kt->language); + kt->language = NULL; + } + if (kt->category) { + g_free (kt->category); + kt->category = NULL; + } + + GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); +} + +static void +gst_kate_tag_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstKateTag *kt = GST_KATE_TAG (object); + const char *str; + + switch (prop_id) { + case ARG_LANGUAGE: + if (kt->language) { + g_free (kt->language); + kt->language = NULL; + } + str = g_value_get_string (value); + if (str) + kt->language = g_strdup (str); + break; + case ARG_CATEGORY: + if (kt->category) { + g_free (kt->category); + kt->category = NULL; + } + str = g_value_get_string (value); + if (str) + kt->category = g_strdup (str); + break; + case ARG_ORIGINAL_CANVAS_WIDTH: + kt->original_canvas_width = g_value_get_int (value); + break; + case ARG_ORIGINAL_CANVAS_HEIGHT: + kt->original_canvas_height = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_kate_tag_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstKateTag *kt = GST_KATE_TAG (object); + + switch (prop_id) { + case ARG_LANGUAGE: + g_value_set_string (value, kt->language ? kt->language : ""); + break; + case ARG_CATEGORY: + g_value_set_string (value, kt->category ? kt->category : ""); + break; + case ARG_ORIGINAL_CANVAS_WIDTH: + g_value_set_int (value, kt->original_canvas_width); + break; + case ARG_ORIGINAL_CANVAS_HEIGHT: + g_value_set_int (value, kt->original_canvas_height); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static guint16 +encode_canvas_size (size_t size) +{ + size_t base = size; + size_t shift = 0; + int value; + + while (base & ~((1 << 12) - 1)) { + /* we have a high bit we can't fit, increase shift if we wouldn't lose low bits */ + if ((size >> shift) & 1) + return 0; + ++shift; + base >>= 1; + } + if (G_UNLIKELY (shift >= 16)) + return 0; + + /* the size can be represented in our encoding */ + value = (base << 4) | shift; + + return (guint16) value; +} + +static GstFlowReturn +gst_kate_tag_parse_packet (GstKateParse * parse, GstBuffer * buffer) +{ + GstTagList *old_tags, *new_tags; + const GstTagList *user_tags; + GstKateTag *kt; + gchar *encoder = NULL; + GstBuffer *new_buf; + + kt = GST_KATE_TAG (parse); + + /* rewrite the language and category */ + if (GST_BUFFER_SIZE (buffer) >= 64 && GST_BUFFER_DATA (buffer)[0] == 0x80) { + buffer = gst_buffer_copy (buffer); + + /* language is at offset 32, 16 bytes, zero terminated */ + if (kt->language) { + strncpy ((char *) GST_BUFFER_DATA (buffer) + 32, kt->language, 15); + GST_BUFFER_DATA (buffer)[47] = 0; + } + /* category is at offset 48, 16 bytes, zero terminated */ + if (kt->category) { + strncpy ((char *) GST_BUFFER_DATA (buffer) + 48, kt->category, 15); + GST_BUFFER_DATA (buffer)[63] = 0; + } + if (kt->original_canvas_width >= 0) { + guint16 v = encode_canvas_size (kt->original_canvas_width); + GST_BUFFER_DATA (buffer)[16] = v & 0xff; + GST_BUFFER_DATA (buffer)[17] = (v >> 8) & 0xff; + } + if (kt->original_canvas_height >= 0) { + guint16 v = encode_canvas_size (kt->original_canvas_height); + GST_BUFFER_DATA (buffer)[18] = v & 0xff; + GST_BUFFER_DATA (buffer)[19] = (v >> 8) & 0xff; + } + } + + /* rewrite the comments packet */ + if (GST_BUFFER_SIZE (buffer) >= 9 && GST_BUFFER_DATA (buffer)[0] == 0x81) { + old_tags = + gst_tag_list_from_vorbiscomment_buffer (buffer, + (const guint8 *) "\201kate\0\0\0\0", 9, &encoder); + user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (kt)); + + /* build new tag list */ + new_tags = gst_tag_list_merge (user_tags, old_tags, + gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (kt))); + gst_tag_list_free (old_tags); + + new_buf = + gst_tag_list_to_vorbiscomment_buffer (new_tags, + (const guint8 *) "\201kate\0\0\0\0", 9, encoder); + gst_buffer_copy_metadata (new_buf, buffer, GST_BUFFER_COPY_TIMESTAMPS); + + gst_tag_list_free (new_tags); + g_free (encoder); + gst_buffer_unref (buffer); + + /* the buffer will have the framing bit used by Vorbis, but we don't use it */ + --GST_BUFFER_SIZE (new_buf); + + buffer = new_buf; + } + + return GST_KATE_PARSE_CLASS (parent_class)->parse_packet (parse, buffer); +} diff --git a/ext/kate/gstkatetag.h b/ext/kate/gstkatetag.h new file mode 100644 index 0000000000..2f27df380c --- /dev/null +++ b/ext/kate/gstkatetag.h @@ -0,0 +1,65 @@ +/* -*- c-basic-offset: 2 -*- + * GStreamer + * Copyright (C) <2006> James Livingston + * Copyright (C) <2008> Vincent Penquerc'h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_KATE_TAG_H__ +#define __GST_KATE_TAG_H__ + +#include "gstkateparse.h" + +G_BEGIN_DECLS +#define GST_TYPE_KATE_TAG \ + (gst_kate_tag_get_type()) +#define GST_KATE_TAG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_KATE_TAG,GstKateTag)) +#define GST_KATE_TAG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_KATE_TAG,GstKateTagClass)) +#define GST_IS_KATE_TAG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_KATE_TAG)) +#define GST_IS_KATE_TAG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_KATE_TAG)) +typedef struct _GstKateTag GstKateTag; +typedef struct _GstKateTagClass GstKateTagClass; + +/** + * GstKateTag: + * + * Opaque data structure. + */ +struct _GstKateTag +{ + GstKateParse parse; + + gchar *language; + gchar *category; + gint original_canvas_width; + gint original_canvas_height; +}; + +struct _GstKateTagClass +{ + GstKateParseClass parent_class; +}; + +GType gst_kate_tag_get_type (void); + +G_END_DECLS +#endif /* __GST_KATE_TAG_H__ */ diff --git a/ext/kate/gstkatetiger.c b/ext/kate/gstkatetiger.c new file mode 100644 index 0000000000..28360dbee8 --- /dev/null +++ b/ext/kate/gstkatetiger.c @@ -0,0 +1,802 @@ +/* + * GStreamer + * Copyright 2005 Thomas Vander Stichele + * Copyright 2005 Ronald S. Bultje + * Copyright 2008 Vincent Penquerc'h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-tiger + * @see_also: katedec + * + * + * + * This element decodes and renders Kate streams + * Kate is a free codec + * for text based data, such as subtitles. Any number of kate streams can be + * embedded in an Ogg stream. + * + * + * libkate (see above url) and libtiger + * are needed to build this element. + * + * Example pipeline + * + * This pipeline renders a Kate stream on top of a Theora video multiplexed + * in the same stream: + * + * gst-launch \ + * filesrc location=video.ogg ! oggdemux name=demux \ + * demux. ! queue ! theoradec ! ffmpegcolorspace ! tiger name=tiger \ + * demux. ! queue ! kateparse ! tiger. \ + * tiger. ! ffmpegcolorspace ! autovideosink + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include "gstkate.h" +#include "gstkatetiger.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_katetiger_debug); +#define GST_CAT_DEFAULT gst_katetiger_debug + +/* Filter signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + ARG_DEFAULT_FONT_DESC = DECODER_BASE_ARG_COUNT, + ARG_QUALITY, + ARG_DEFAULT_FONT_EFFECT, + ARG_DEFAULT_FONT_EFFECT_STRENGTH, + ARG_DEFAULT_FONT_RED, + ARG_DEFAULT_FONT_GREEN, + ARG_DEFAULT_FONT_BLUE, + ARG_DEFAULT_FONT_ALPHA, + ARG_DEFAULT_BACKGROUND_RED, + ARG_DEFAULT_BACKGROUND_GREEN, + ARG_DEFAULT_BACKGROUND_BLUE, + ARG_DEFAULT_BACKGROUND_ALPHA, +}; + +static GstStaticPadTemplate kate_sink_factory = +GST_STATIC_PAD_TEMPLATE ("kate_sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_KATE_MIME_TYPE) + ); + +static GstStaticPadTemplate video_sink_factory = +GST_STATIC_PAD_TEMPLATE ("video_sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-rgb, bpp=(int)32, depth=(int)24") + ); + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-rgb, bpp=(int)32, depth=(int)24") + ); + +GST_BOILERPLATE (GstKateTiger, gst_kate_tiger, GstElement, GST_TYPE_ELEMENT); + +static GType +gst_kate_tiger_font_effect_get_type () +{ + static GType font_effect_type = 0; + + if (!font_effect_type) { + static const GEnumValue font_effects[] = { + {tiger_font_plain, "none", "none"}, + {tiger_font_shadow, "shadow", "shadow"}, + {tiger_font_outline, "outline", "outline"}, + {0, NULL, NULL} + }; + font_effect_type = g_enum_register_static ("GstFontEffect", font_effects); + } + + return font_effect_type; +} + +static void gst_kate_tiger_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_kate_tiger_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_kate_tiger_dispose (GObject * object); + +static GstFlowReturn gst_kate_tiger_kate_chain (GstPad * pad, GstBuffer * buf); +static GstFlowReturn gst_kate_tiger_video_chain (GstPad * pad, GstBuffer * buf); +static GstStateChangeReturn gst_kate_tiger_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_kate_tiger_kate_sink_query (GstPad * pad, GstQuery * query); +static gboolean gst_kate_tiger_kate_event (GstPad * pad, GstEvent * event); +static gboolean gst_kate_tiger_video_set_caps (GstPad * pad, GstCaps * caps); +static gboolean gst_kate_tiger_source_event (GstPad * pad, GstEvent * event); + +static void +gst_kate_tiger_base_init (gpointer gclass) +{ + static GstElementDetails element_details = + GST_ELEMENT_DETAILS ("Kate stream renderer", + "Codec/Decoder/Video/Overlay", + "Decodes and renders Kate streams on top of a video", + "Vincent Penquerc'h "); + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&kate_sink_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&video_sink_factory)); + gst_element_class_set_details (element_class, &element_details); +} + +/* initialize the plugin's class */ +static void +gst_kate_tiger_class_init (GstKateTigerClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_kate_tiger_get_property); + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_kate_tiger_set_property); + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_kate_tiger_dispose); + + gst_kate_util_install_decoder_base_properties (gobject_class); + + g_object_class_install_property (gobject_class, ARG_QUALITY, + g_param_spec_double ("quality", "Rendering quality", + "Rendering quality (0 is faster, 1 is best and slower)", + 0.0, 1.0, 1.0, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_FONT_DESC, + g_param_spec_string ("default-font-desc", "Default font description", + "Default font description (Pango style) to render text with", + "", G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_FONT_EFFECT, + g_param_spec_enum ("default-font-effect", "Default font effect", + "Whether to apply an effect to text by default, for increased readability", + gst_kate_tiger_font_effect_get_type (), + tiger_font_plain, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (gobject_class, + ARG_DEFAULT_FONT_EFFECT_STRENGTH, + g_param_spec_double ("default-font-effect-strength", + "Default font effect strength", + "How pronounced should the font effect be (effect dependent)", 0.0, + 1.0, 0.5, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_FONT_RED, + g_param_spec_int ("default-font-red", + "Default font color (red component)", + "Default font color (red component, between 0 and 255) to render text with", + 0, 255, 255, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_FONT_GREEN, + g_param_spec_int ("default-font-green", + "Default font color (green component)", + "Default font color (green component, between 0 and 255) to render text with", + 0, 255, 255, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_FONT_BLUE, + g_param_spec_int ("default-font-blue", + "Default font color (blue component)", + "Default font color (blue component, between 0 and 255) to render text with", + 0, 255, 255, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_FONT_ALPHA, + g_param_spec_int ("default-font-alpha", + "Default font color (alpha component)", + "Default font color (alpha component, between 0 and 255) to render text with", + 0, 255, 255, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_BACKGROUND_RED, + g_param_spec_int ("default-background-red", + "Default background color (red component)", + "Default background color (red component, between 0 and 255) to render text with", + 0, 255, 255, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_BACKGROUND_GREEN, + g_param_spec_int ("default-background-green", + "Default background color (green component)", + "Default background color (green component, between 0 and 255) to render text with", + 0, 255, 255, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_BACKGROUND_BLUE, + g_param_spec_int ("default-background-blue", + "Default background color (blue component)", + "Default background color (blue component, between 0 and 255) to render text with", + 0, 255, 255, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEFAULT_BACKGROUND_ALPHA, + g_param_spec_int ("default-background-alpha", + "Default background color (alpha component)", + "Default background color (alpha component, between 0 and 255) to render text with", + 0, 255, 255, G_PARAM_READWRITE)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_kate_tiger_change_state); +} + +/* initialize the new element + * instantiate pads and add them to element + * set functions + * initialize structure + */ +static void +gst_kate_tiger_init (GstKateTiger * tiger, GstKateTigerClass * gclass) +{ + GST_DEBUG_OBJECT (tiger, "gst_kate_tiger_init"); + + tiger->mutex = g_mutex_new (); + + tiger->katesinkpad = + gst_pad_new_from_static_template (&kate_sink_factory, "kate_sink"); + gst_pad_set_chain_function (tiger->katesinkpad, + GST_DEBUG_FUNCPTR (gst_kate_tiger_kate_chain)); + gst_pad_set_query_function (tiger->katesinkpad, + GST_DEBUG_FUNCPTR (gst_kate_tiger_kate_sink_query)); + gst_pad_use_fixed_caps (tiger->katesinkpad); + gst_pad_set_caps (tiger->katesinkpad, + gst_static_pad_template_get_caps (&kate_sink_factory)); + gst_pad_set_event_function (tiger->katesinkpad, gst_kate_tiger_kate_event); + gst_element_add_pad (GST_ELEMENT (tiger), tiger->katesinkpad); + + tiger->videosinkpad = + gst_pad_new_from_static_template (&video_sink_factory, "video_sink"); + gst_pad_set_chain_function (tiger->videosinkpad, + GST_DEBUG_FUNCPTR (gst_kate_tiger_video_chain)); + //gst_pad_set_query_function (tiger->videosinkpad, GST_DEBUG_FUNCPTR (gst_kate_tiger_video_sink_query)); + gst_pad_use_fixed_caps (tiger->videosinkpad); + gst_pad_set_caps (tiger->videosinkpad, + gst_static_pad_template_get_caps (&video_sink_factory)); + gst_pad_set_setcaps_function (tiger->videosinkpad, + GST_DEBUG_FUNCPTR (gst_kate_tiger_video_set_caps)); + gst_element_add_pad (GST_ELEMENT (tiger), tiger->videosinkpad); + + tiger->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); + gst_pad_set_event_function (tiger->srcpad, gst_kate_tiger_source_event); + gst_element_add_pad (GST_ELEMENT (tiger), tiger->srcpad); + + gst_kate_util_decode_base_init (&tiger->decoder); + + tiger->tr = NULL; + + tiger->default_font_desc = NULL; + tiger->quality = -1.0; + tiger->default_font_effect = tiger_font_plain; + tiger->default_font_effect_strength = 0.5; + tiger->default_font_r = 255; + tiger->default_font_g = 255; + tiger->default_font_b = 255; + tiger->default_font_a = 255; + tiger->default_background_r = 0; + tiger->default_background_g = 0; + tiger->default_background_b = 0; + tiger->default_background_a = 0; + + tiger->video_width = 0; + tiger->video_height = 0; +} + +static void +gst_kate_tiger_dispose (GObject * object) +{ + GstKateTiger *tiger = GST_KATE_TIGER (object); + + GST_LOG_OBJECT (tiger, "disposing"); + + if (tiger->default_font_desc) { + g_free (tiger->default_font_desc); + tiger->default_font_desc = NULL; + } + + g_mutex_free (tiger->mutex); + tiger->mutex = NULL; + + GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); +} + +static void +gst_kate_tiger_update_quality (GstKateTiger * tiger) +{ + if (tiger->tr && tiger->quality >= 0.0) { + tiger_renderer_set_quality (tiger->tr, tiger->quality); + } +} + +static void +gst_kate_tiger_update_default_font_effect (GstKateTiger * tiger) +{ + if (tiger->tr) { + tiger_renderer_set_default_font_effect (tiger->tr, + tiger->default_font_effect, tiger->default_font_effect_strength); + } +} + +static void +gst_kate_tiger_update_default_font_color (GstKateTiger * tiger) +{ + if (tiger->tr) { + tiger_renderer_set_default_font_color (tiger->tr, + tiger->default_font_r / 255.0, + tiger->default_font_g / 255.0, + tiger->default_font_b / 255.0, tiger->default_font_a / 255.0); + } +} + +static void +gst_kate_tiger_update_default_background_color (GstKateTiger * tiger) +{ + if (tiger->tr) { + tiger_renderer_set_default_background_fill_color (tiger->tr, + tiger->default_background_r / 255.0, + tiger->default_background_g / 255.0, + tiger->default_background_b / 255.0, + tiger->default_background_a / 255.0); + } +} + +static void +gst_kate_tiger_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstKateTiger *tiger = GST_KATE_TIGER (object); + const char *str; + + g_mutex_lock (tiger->mutex); + + switch (prop_id) { + case ARG_DEFAULT_FONT_DESC: + if (tiger->default_font_desc) { + g_free (tiger->default_font_desc); + tiger->default_font_desc = NULL; + } + str = g_value_get_string (value); + if (str) { + tiger->default_font_desc = g_strdup (str); + if (tiger->tr) + tiger_renderer_set_default_font_description (tiger->tr, + tiger->default_font_desc); + } + break; + case ARG_QUALITY: + tiger->quality = g_value_get_double (value); + gst_kate_tiger_update_quality (tiger); + break; + case ARG_DEFAULT_FONT_EFFECT: + tiger->default_font_effect = g_value_get_enum (value); + gst_kate_tiger_update_default_font_effect (tiger); + break; + case ARG_DEFAULT_FONT_EFFECT_STRENGTH: + tiger->default_font_effect_strength = g_value_get_double (value); + gst_kate_tiger_update_default_font_effect (tiger); + break; + case ARG_DEFAULT_FONT_RED: + tiger->default_font_r = g_value_get_int (value); + gst_kate_tiger_update_default_font_color (tiger); + break; + case ARG_DEFAULT_FONT_GREEN: + tiger->default_font_g = g_value_get_int (value); + gst_kate_tiger_update_default_font_color (tiger); + break; + case ARG_DEFAULT_FONT_BLUE: + tiger->default_font_b = g_value_get_int (value); + gst_kate_tiger_update_default_font_color (tiger); + break; + case ARG_DEFAULT_FONT_ALPHA: + tiger->default_font_a = g_value_get_int (value); + gst_kate_tiger_update_default_font_color (tiger); + break; + case ARG_DEFAULT_BACKGROUND_RED: + tiger->default_background_r = g_value_get_int (value); + gst_kate_tiger_update_default_background_color (tiger); + break; + case ARG_DEFAULT_BACKGROUND_GREEN: + tiger->default_background_g = g_value_get_int (value); + gst_kate_tiger_update_default_background_color (tiger); + break; + case ARG_DEFAULT_BACKGROUND_BLUE: + tiger->default_background_b = g_value_get_int (value); + gst_kate_tiger_update_default_background_color (tiger); + break; + case ARG_DEFAULT_BACKGROUND_ALPHA: + tiger->default_background_a = g_value_get_int (value); + gst_kate_tiger_update_default_background_color (tiger); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + g_mutex_unlock (tiger->mutex); +} + +static void +gst_kate_tiger_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstKateTiger *tiger = GST_KATE_TIGER (object); + + g_mutex_lock (tiger->mutex); + + switch (prop_id) { + case ARG_DEFAULT_FONT_DESC: + g_value_set_string (value, + tiger->default_font_desc ? tiger->default_font_desc : ""); + break; + case ARG_QUALITY: + g_value_set_double (value, tiger->quality); + break; + case ARG_DEFAULT_FONT_EFFECT: + g_value_set_enum (value, tiger->default_font_effect); + break; + case ARG_DEFAULT_FONT_EFFECT_STRENGTH: + g_value_set_double (value, tiger->default_font_effect_strength); + break; + case ARG_DEFAULT_FONT_RED: + g_value_set_int (value, tiger->default_font_r); + break; + case ARG_DEFAULT_FONT_GREEN: + g_value_set_int (value, tiger->default_font_g); + break; + case ARG_DEFAULT_FONT_BLUE: + g_value_set_int (value, tiger->default_font_b); + break; + case ARG_DEFAULT_FONT_ALPHA: + g_value_set_int (value, tiger->default_font_a); + break; + case ARG_DEFAULT_BACKGROUND_RED: + g_value_set_int (value, tiger->default_background_r); + break; + case ARG_DEFAULT_BACKGROUND_GREEN: + g_value_set_int (value, tiger->default_background_g); + break; + case ARG_DEFAULT_BACKGROUND_BLUE: + g_value_set_int (value, tiger->default_background_b); + break; + case ARG_DEFAULT_BACKGROUND_ALPHA: + g_value_set_int (value, tiger->default_background_a); + break; + default: + if (!gst_kate_util_decoder_base_get_property (&tiger->decoder, object, + prop_id, value, pspec)) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } + + g_mutex_unlock (tiger->mutex); +} + +/* GstElement vmethod implementations */ + +/* chain function + * this function does the actual processing + */ + +static GstFlowReturn +gst_kate_tiger_kate_chain (GstPad * pad, GstBuffer * buf) +{ + GstKateTiger *tiger = GST_KATE_TIGER (gst_pad_get_parent (pad)); + const kate_event *ev = NULL; + GstFlowReturn rflow = GST_FLOW_OK; + + g_mutex_lock (tiger->mutex); + + GST_LOG_OBJECT (tiger, "Got kate buffer"); + + rflow = + gst_kate_util_decoder_base_chain_kate_packet (&tiger->decoder, + GST_ELEMENT_CAST (tiger), pad, buf, tiger->srcpad, &ev); + if (G_LIKELY (rflow == GST_FLOW_OK)) { + if (ev) { + int ret = tiger_renderer_add_event (tiger->tr, ev->ki, ev); + GST_INFO_OBJECT (tiger, "adding event for %p from %f to %f: %p, \"%s\"", + ev->ki, ev->start_time, ev->end_time, ev->bitmap, ev->text); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (tiger, + "failed to add Kate event to Tiger renderer: %d", ret); + } + } + } + + gst_object_unref (tiger); + gst_buffer_unref (buf); + + g_mutex_unlock (tiger->mutex); + + return rflow; +} + +static gboolean +gst_kate_tiger_video_set_caps (GstPad * pad, GstCaps * caps) +{ + GstKateTiger *tiger = GST_KATE_TIGER (gst_pad_get_parent (pad)); + GstStructure *s; + gint w, h; + gboolean res = FALSE; + + g_mutex_lock (tiger->mutex); + + s = gst_caps_get_structure (caps, 0); + + if (G_LIKELY (gst_structure_get_int (s, "width", &w)) + && G_LIKELY (gst_structure_get_int (s, "height", &h))) { + GST_INFO_OBJECT (tiger, "video sink: %d %d", w, h); + tiger->video_width = w; + tiger->video_height = h; + res = TRUE; + } + + g_mutex_unlock (tiger->mutex); + + gst_object_unref (tiger); + return TRUE; +} + +static GstFlowReturn +gst_kate_tiger_video_chain (GstPad * pad, GstBuffer * buf) +{ + GstKateTiger *tiger = GST_KATE_TIGER (gst_pad_get_parent (pad)); + GstFlowReturn rflow = GST_FLOW_OK; + unsigned char *ptr; + int ret; + + g_mutex_lock (tiger->mutex); + + GST_LOG_OBJECT (tiger, "got video frame, %u bytes", GST_BUFFER_SIZE (buf)); + + /* draw on it */ + buf = gst_buffer_make_writable (buf); + if (G_UNLIKELY (!buf)) { + GST_WARNING_OBJECT (tiger, "Failed to make video buffer writable"); + } else { + ptr = GST_BUFFER_DATA (buf); + if (!ptr) { + GST_WARNING_OBJECT (tiger, + "Failed to get a pointer to video buffer data"); + } else { + ret = tiger_renderer_set_buffer (tiger->tr, ptr, tiger->video_width, tiger->video_height, tiger->video_width * 4, 0); // TODO: stride ? + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (tiger, + "Tiger renderer failed to set buffer to video frame: %d", ret); + } else { + kate_float t = GST_BUFFER_TIMESTAMP (buf) / (gdouble) GST_SECOND; + ret = tiger_renderer_update (tiger->tr, t, 1); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (tiger, "Tiger renderer failed to update: %d", + ret); + } else { + ret = tiger_renderer_render (tiger->tr); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (tiger, + "Tiger renderer failed to render to video frame: %d", ret); + } else { + GST_LOG_OBJECT (tiger, + "Tiger renderer rendered on video frame at %f", t); + } + } + } + } + } + rflow = gst_pad_push (tiger->srcpad, buf); + + gst_object_unref (tiger); + + g_mutex_unlock (tiger->mutex); + + return rflow; +} + +static GstStateChangeReturn +gst_kate_tiger_change_state (GstElement * element, GstStateChange transition) +{ + GstKateTiger *tiger = GST_KATE_TIGER (element); + GstStateChangeReturn res; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT (tiger, "PAUSED -> READY, clearing kate state"); + g_mutex_lock (tiger->mutex); + if (tiger->tr) { + tiger_renderer_destroy (tiger->tr); + tiger->tr = NULL; + } + g_mutex_unlock (tiger->mutex); + break; + default: + break; + } + + res = + gst_kate_decoder_base_change_state (&tiger->decoder, element, + parent_class, transition); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG_OBJECT (tiger, "READY -> PAUSED, initializing kate state"); + g_mutex_lock (tiger->mutex); + if (tiger->decoder.initialized) { + int ret = tiger_renderer_create (&tiger->tr); + if (ret < 0) { + GST_WARNING_OBJECT (tiger, "failed to create tiger renderer: %d", + ret); + } else { + ret = + tiger_renderer_set_default_font_description (tiger->tr, + tiger->default_font_desc); + if (ret < 0) { + GST_WARNING_OBJECT (tiger, + "failed to set tiger default font description: %d", ret); + } + gst_kate_tiger_update_default_font_color (tiger); + gst_kate_tiger_update_default_background_color (tiger); + gst_kate_tiger_update_default_font_effect (tiger); + gst_kate_tiger_update_quality (tiger); + } + } + g_mutex_unlock (tiger->mutex); + break; + default: + break; + } + + return res; +} + +static gboolean +gst_kate_tiger_seek (GstKateTiger * tiger, GstPad * pad, GstEvent * event) +{ + GstFormat format; + gdouble rate; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + + /* forward to both sinks */ + gst_event_ref (event); + if (gst_pad_push_event (tiger->videosinkpad, event)) { + if (gst_pad_push_event (tiger->katesinkpad, event)) { + if (format == GST_FORMAT_TIME) { + /* if seeking in time, we can update tiger to remove any appropriate events */ + kate_float target = cur / (gdouble) GST_SECOND; + GST_INFO_OBJECT (tiger, "Seeking in time to %f", target); + g_mutex_lock (tiger->mutex); + tiger_renderer_seek (tiger->tr, target); + g_mutex_unlock (tiger->mutex); + } + return TRUE; + } else { + return FALSE; + } + } else { + gst_event_unref (event); + return FALSE; + } +} + +static gboolean +gst_kate_tiger_source_event (GstPad * pad, GstEvent * event) +{ + GstKateTiger *tiger = + (GstKateTiger *) (gst_object_get_parent (GST_OBJECT (pad))); + gboolean res = TRUE; + + g_return_val_if_fail (tiger != NULL, FALSE); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + GST_INFO_OBJECT (tiger, "Seek on source pad"); + res = gst_kate_tiger_seek (tiger, pad, event); + break; + default: + res = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (tiger); + + return res; +} + +static gboolean +gst_kate_tiger_kate_event (GstPad * pad, GstEvent * event) +{ + GstKateTiger *tiger = + (GstKateTiger *) (gst_object_get_parent (GST_OBJECT (pad))); + gboolean res = TRUE; + + g_return_val_if_fail (tiger != NULL, FALSE); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + GST_INFO_OBJECT (tiger, "New segment on Kate pad"); + gst_event_unref (event); + break; + case GST_EVENT_EOS: + /* we ignore this, it just means we don't have anymore Kate packets, but + the Tiger renderer will still draw (if appropriate) on incoming video */ + GST_INFO_OBJECT (tiger, "EOS on Kate pad"); + gst_event_unref (event); + break; + default: + res = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (tiger); + + return res; +} + +gboolean +gst_kate_tiger_kate_sink_query (GstPad * pad, GstQuery * query) +{ + GstKateTiger *tiger = GST_KATE_TIGER (gst_pad_get_parent (pad)); + gboolean res = gst_kate_decoder_base_sink_query (&tiger->decoder, + GST_ELEMENT_CAST (tiger), pad, query); + gst_object_unref (tiger); + return res; +} diff --git a/ext/kate/gstkatetiger.h b/ext/kate/gstkatetiger.h new file mode 100644 index 0000000000..0ab7dc136a --- /dev/null +++ b/ext/kate/gstkatetiger.h @@ -0,0 +1,108 @@ +/* + * GStreamer + * Copyright 2005 Thomas Vander Stichele + * Copyright 2005 Ronald S. Bultje + * Copyright 2008 Vincent Penquerc'h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Alternatively, the contents of this file may be used under the + * GNU Lesser General Public License Version 2.1 (the "LGPL"), in + * which case the following provisions apply instead of the ones + * mentioned above: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_KATE_TIGER_H__ +#define __GST_KATE_TIGER_H__ + +#include +#include +#include +#include "gstkateutil.h" + +G_BEGIN_DECLS +/* #defines don't like whitespacey bits */ +#define GST_TYPE_KATE_TIGER \ + (gst_kate_tiger_get_type()) +#define GST_KATE_TIGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_KATE_TIGER,GstKateTiger)) +#define GST_KATE_TIGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_KATE,GstKateTigerClass)) +#define GST_IS_KATE_TIGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_KATE_TIGER)) +#define GST_IS_KATE_TIGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_KATE_TIGER)) +typedef struct _GstKateTiger GstKateTiger; +typedef struct _GstKateTigerClass GstKateTigerClass; + +struct _GstKateTiger +{ + GstElement element; + + GstPad *katesinkpad; + GstPad *videosinkpad; + GstPad *srcpad; + + GstKateDecoderBase decoder; + + tiger_renderer *tr; + + gdouble quality; + gchar *default_font_desc; + gboolean default_font_effect; + gdouble default_font_effect_strength; + guchar default_font_r; + guchar default_font_g; + guchar default_font_b; + guchar default_font_a; + guchar default_background_r; + guchar default_background_g; + guchar default_background_b; + guchar default_background_a; + + gint video_width; + gint video_height; + + GMutex *mutex; +}; + +struct _GstKateTigerClass +{ + GstElementClass parent_class; +}; + +GType gst_kate_tiger_get_type (void); + +G_END_DECLS +#endif /* __GST_KATE_TIGER_H__ */ diff --git a/ext/kate/gstkateutil.c b/ext/kate/gstkateutil.c new file mode 100644 index 0000000000..2928771d6f --- /dev/null +++ b/ext/kate/gstkateutil.c @@ -0,0 +1,371 @@ +/* GStreamer + * Copyright (C) 2008 Vincent Penquerc'h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "gstkate.h" +#include "gstkateutil.h" + + +GstCaps * +gst_kate_util_set_header_on_caps (GstElement * element, GstCaps * caps, + GList * headers) +{ + GstStructure *structure; + GValue array = { 0 }; + + if (G_UNLIKELY (!caps)) + return NULL; + if (G_UNLIKELY (!headers)) + return NULL; + + caps = gst_caps_make_writable (caps); + structure = gst_caps_get_structure (caps, 0); + + g_value_init (&array, GST_TYPE_ARRAY); + + while (headers) { + GValue value = { 0 }; + GstBuffer *buffer = headers->data; + g_assert (buffer); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_IN_CAPS); + g_value_init (&value, GST_TYPE_BUFFER); + /* as in theoraenc, we need to copy to avoid circular references */ + buffer = gst_buffer_copy (buffer); + gst_value_set_buffer (&value, buffer); + gst_buffer_unref (buffer); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + headers = headers->next; + } + + gst_structure_set_value (structure, "streamheader", &array); + g_value_unset (&array); + GST_LOG_OBJECT (element, "here are the newly set caps: %" GST_PTR_FORMAT, + caps); + + return caps; +} + +void +gst_kate_util_install_decoder_base_properties (GObjectClass * gobject_class) +{ + g_object_class_install_property (gobject_class, ARG_DEC_BASE_LANGUAGE, + g_param_spec_string ("language", "Language", "The language of the stream", + "", G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, ARG_DEC_BASE_CATEGORY, + g_param_spec_string ("category", "Category", "The category of the stream", + "", G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, + ARG_DEC_BASE_ORIGINAL_CANVAS_WIDTH, + g_param_spec_int ("original-canvas-width", + "Original canvas width (0 is unspecified)", + "The canvas width this stream was authored for", 0, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property (gobject_class, + ARG_DEC_BASE_ORIGINAL_CANVAS_HEIGHT, + g_param_spec_int ("original-canvas-height", "Original canvas height", + "The canvas height this stream was authored for (0 is unspecified)", + 0, G_MAXINT, 0, G_PARAM_READABLE)); +} + +void +gst_kate_util_decode_base_init (GstKateDecoderBase * decoder) +{ + if (G_UNLIKELY (!decoder)) + return; + + decoder->language = NULL; + decoder->category = NULL; + decoder->original_canvas_width = 0; + decoder->original_canvas_height = 0; + decoder->tags = NULL; + decoder->initialized = FALSE; +} + +gboolean +gst_kate_util_decoder_base_get_property (GstKateDecoderBase * decoder, + GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) +{ + gboolean res = TRUE; + switch (prop_id) { + case ARG_DEC_BASE_LANGUAGE: + g_value_set_string (value, decoder->language); + break; + case ARG_DEC_BASE_CATEGORY: + g_value_set_string (value, decoder->category); + break; + case ARG_DEC_BASE_ORIGINAL_CANVAS_WIDTH: + g_value_set_int (value, decoder->original_canvas_width); + break; + case ARG_DEC_BASE_ORIGINAL_CANVAS_HEIGHT: + g_value_set_int (value, decoder->original_canvas_height); + break; + default: + res = FALSE; + break; + } + return res; +} + +GstFlowReturn +gst_kate_util_decoder_base_chain_kate_packet (GstKateDecoderBase * decoder, + GstElement * element, GstPad * pad, GstBuffer * buf, GstPad * srcpad, + const kate_event ** ev) +{ + kate_packet kp; + int ret; + GstFlowReturn rflow = GST_FLOW_OK; + + GST_DEBUG_OBJECT (element, "got kate packet, %u bytes, type %02x", + GST_BUFFER_SIZE (buf), + GST_BUFFER_SIZE (buf) == 0 ? -1 : GST_BUFFER_DATA (buf)[0]); + kate_packet_wrap (&kp, GST_BUFFER_SIZE (buf), GST_BUFFER_DATA (buf)); + ret = kate_high_decode_packetin (&decoder->k, &kp, ev); + if (G_UNLIKELY (ret < 0)) { + GST_WARNING_OBJECT (element, "kate_high_decode_packetin failed (%d)", ret); + return GST_FLOW_ERROR; + } else if (G_UNLIKELY (ret > 0)) { + GST_DEBUG_OBJECT (element, + "kate_high_decode_packetin has received EOS packet"); + return GST_FLOW_OK; + } + + /* headers may be interesting to retrieve information from */ + if (G_LIKELY (GST_BUFFER_SIZE (buf) > 0)) + switch (GST_BUFFER_DATA (buf)[0]) { + GstCaps *caps; + + case 0x80: /* ID header */ + GST_INFO_OBJECT (element, "Parsed ID header: language %s, category %s", + decoder->k.ki->language, decoder->k.ki->category); + caps = gst_caps_new_simple ("text/x-pango-markup", NULL); + gst_pad_set_caps (srcpad, caps); + gst_caps_unref (caps); + if (decoder->k.ki->language && *decoder->k.ki->language) { + GstTagList *tags = gst_tag_list_new (); + if (tags) { + gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_LANGUAGE_CODE, + decoder->k.ki->language, NULL); + // TODO: category - where should it go ? + decoder->tags = + gst_tag_list_merge (decoder->tags, tags, GST_TAG_MERGE_REPLACE); + gst_tag_list_free (tags); + } + } + + /* update properties */ + if (decoder->language) + g_free (decoder->language); + decoder->language = g_strdup (decoder->k.ki->language); + if (decoder->category) + g_free (decoder->category); + decoder->category = g_strdup (decoder->k.ki->category); + decoder->original_canvas_width = decoder->k.ki->original_canvas_width; + decoder->original_canvas_height = decoder->k.ki->original_canvas_height; + + break; + + case 0x81: /* Vorbis comments header */ + GST_INFO_OBJECT (element, "Parsed comments header"); + { + gchar *encoder = NULL; + GstTagList *list = gst_tag_list_from_vorbiscomment_buffer (buf, + (const guint8 *) "\201kate\0\0\0\0", 9, &encoder); + if (list) { + decoder->tags = + gst_tag_list_merge (decoder->tags, list, GST_TAG_MERGE_REPLACE); + gst_tag_list_free (list); + } + + if (!decoder->tags) { + GST_ERROR_OBJECT (element, "failed to decode comment header"); + decoder->tags = gst_tag_list_new (); + } + if (encoder) { + gst_tag_list_add (decoder->tags, GST_TAG_MERGE_REPLACE, + GST_TAG_ENCODER, encoder, NULL); + g_free (encoder); + } + gst_tag_list_add (decoder->tags, GST_TAG_MERGE_REPLACE, GST_TAG_CODEC, + "kate", NULL); + gst_tag_list_add (decoder->tags, GST_TAG_MERGE_REPLACE, + GST_TAG_ENCODER_VERSION, decoder->k.ki->bitstream_version_major, + NULL); + + if (decoder->initialized) { + gst_element_found_tags_for_pad (element, srcpad, decoder->tags); + decoder->tags = NULL; + } else { + /* Only push them as messages for the time being. * + * They will be pushed on the pad once the decoder is initialized */ + gst_element_post_message (element, + gst_message_new_tag (GST_OBJECT (element), + gst_tag_list_copy (decoder->tags))); + } + } + break; + + default: + break; + } + + return rflow; +} + +GstStateChangeReturn +gst_kate_decoder_base_change_state (GstKateDecoderBase * decoder, + GstElement * element, GstElementClass * parent_class, + GstStateChange transition) +{ + GstStateChangeReturn res; + int ret; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG_OBJECT (element, "READY -> PAUSED, initializing kate state"); + ret = kate_high_decode_init (&decoder->k); + if (ret < 0) { + GST_WARNING_OBJECT (element, "failed to initialize kate state: %d", + ret); + } + decoder->initialized = TRUE; + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + default: + break; + } + + res = parent_class->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT (element, "PAUSED -> READY, clearing kate state"); + if (decoder->initialized) { + kate_high_decode_clear (&decoder->k); + decoder->initialized = FALSE; + } + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return res; +} + +static GstClockTime +gst_kate_util_granule_time (kate_state * k, gint64 granulepos) +{ + if (G_UNLIKELY (granulepos == -1)) + return -1; + + return kate_granule_time (k->ki, granulepos) * GST_SECOND; +} + +/* +conversions on the sink: + - default is granules at num/den rate (subject to the granule shift) + - default -> time is possible + - bytes do not mean anything, packets can be any number of bytes, and we + have no way to know the number of bytes emitted without decoding +conversions on the source: + - nothing +*/ + +gboolean +gst_kate_decoder_base_convert (GstKateDecoderBase * decoder, + GstElement * element, GstPad * pad, GstFormat src_fmt, gint64 src_val, + GstFormat * dest_fmt, gint64 * dest_val) +{ + gboolean res = FALSE; + + if (src_fmt == *dest_fmt) { + *dest_val = src_val; + return TRUE; + } + + if (!decoder->initialized) { + GST_WARNING_OBJECT (element, "not initialized yet"); + return FALSE; + } + + if (src_fmt == GST_FORMAT_BYTES || *dest_fmt == GST_FORMAT_BYTES) { + GST_WARNING_OBJECT (element, "unsupported format"); + return FALSE; + } + + switch (src_fmt) { + case GST_FORMAT_DEFAULT: + switch (*dest_fmt) { + case GST_FORMAT_TIME: + *dest_val = gst_kate_util_granule_time (&decoder->k, src_val); + res = TRUE; + break; + default: + res = FALSE; + break; + } + break; + default: + res = FALSE; + break; + } + + if (!res) { + GST_WARNING_OBJECT (element, "unsupported format"); + } + + return res; +} + +gboolean +gst_kate_decoder_base_sink_query (GstKateDecoderBase * decoder, + GstElement * element, GstPad * pad, GstQuery * query) +{ + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + if (!gst_kate_decoder_base_convert (decoder, element, pad, src_fmt, + src_val, &dest_fmt, &dest_val)) { + return gst_pad_query_default (pad, query); + } + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + return TRUE; + } + default: + return gst_pad_query_default (pad, query); + } +} diff --git a/ext/kate/gstkateutil.h b/ext/kate/gstkateutil.h new file mode 100644 index 0000000000..6e8ea2e013 --- /dev/null +++ b/ext/kate/gstkateutil.h @@ -0,0 +1,77 @@ +/* -*- c-basic-offset: 2 -*- + * GStreamer + * Copyright (C) <2008> Vincent Penquerc'h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_KATE_UTIL_H__ +#define __GST_KATE_UTIL_H__ + +#include +#include + +G_BEGIN_DECLS enum +{ + ARG_DEC_BASE_0, + ARG_DEC_BASE_LANGUAGE, + ARG_DEC_BASE_CATEGORY, + ARG_DEC_BASE_ORIGINAL_CANVAS_WIDTH, + ARG_DEC_BASE_ORIGINAL_CANVAS_HEIGHT, + DECODER_BASE_ARG_COUNT +}; + +typedef struct +{ + kate_state k; + + gboolean initialized; + + GstTagList *tags; + + gchar *language; + gchar *category; + + gint original_canvas_width; + gint original_canvas_height; + +} GstKateDecoderBase; + +extern GstCaps *gst_kate_util_set_header_on_caps (GstElement * element, + GstCaps * caps, GList * headers); +extern void gst_kate_util_decode_base_init (GstKateDecoderBase * decoder); +extern void gst_kate_util_install_decoder_base_properties (GObjectClass * + gobject_class); +extern gboolean gst_kate_util_decoder_base_get_property (GstKateDecoderBase * + decoder, GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec); +extern GstFlowReturn +gst_kate_util_decoder_base_chain_kate_packet (GstKateDecoderBase * decoder, + GstElement * element, GstPad * pad, GstBuffer * buffer, GstPad * srcpad, + const kate_event ** ev); +extern GstStateChangeReturn +gst_kate_decoder_base_change_state (GstKateDecoderBase * decoder, + GstElement * element, GstElementClass * parent_class, + GstStateChange transition); +extern gboolean gst_kate_decoder_base_convert (GstKateDecoderBase * decoder, + GstElement * element, GstPad * pad, GstFormat src_fmt, gint64 src_val, + GstFormat * dest_fmt, gint64 * dest_val); +extern gboolean gst_kate_decoder_base_sink_query (GstKateDecoderBase * decoder, + GstElement * element, GstPad * pad, GstQuery * query); + +G_END_DECLS +#endif /* __GST_KATE_UTIL_H__ */ diff --git a/gst-plugins-bad.spec.in b/gst-plugins-bad.spec.in index ab02bcaa31..ea4a5d8242 100644 --- a/gst-plugins-bad.spec.in +++ b/gst-plugins-bad.spec.in @@ -158,6 +158,7 @@ rm -rf $RPM_BUILD_ROOT @USE_CELT_TRUE@%{_libdir}/gstreamer-%{majorminor}/libgstcelt.so @USE_MPEG2ENC_TRUE@%{_libdir}/gstreamer-%{majorminor}/libgstmpeg2enc.so @USE_MPLEX_TRUE@%{_libdir}/gstreamer-%{majorminor}/libgstmplex.so +@USE_KATE_TRUE@%{_libdir}/gstreamer-%{majorminor}/libgstkate.so %changelog * Thu Mar 12 2009 Christian Schaller diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index bf4f07ce86..1aa99d1a41 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -63,6 +63,13 @@ else check_timidity= endif +if USE_KATE +check_kate=elements/kate +else +check_kate= +endif + + VALGRIND_TO_FIX = \ elements/mpeg2enc \ elements/mplex @@ -78,6 +85,7 @@ check_PROGRAMS = \ $(check_neon) \ $(check_ofa) \ $(check_timidity) \ + $(check_kate) \ elements/aacparse \ elements/amrparse \ elements/camerabin \ @@ -119,4 +127,7 @@ elements_rtpbin_buffer_list_SOURCES = elements/rtpbin_buffer_list.c elements_timidity_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS) elements_timidity_LDADD = $(GST_BASE_LIBS) $(LDADD) +elements_kate_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS) +elements_kate_LDADD = $(GST_BASE_LIBS) $(LDADD) + EXTRA_DIST = gst-plugins-bad.supp diff --git a/tests/check/elements/kate.c b/tests/check/elements/kate.c new file mode 100644 index 0000000000..f7ac3ed4a1 --- /dev/null +++ b/tests/check/elements/kate.c @@ -0,0 +1,839 @@ +/* GStreamer + * + * unit test for kate + * + * Copyright (C) <2007> Stefan Kost + * Copyright (C) <2008> ogg.k.ogg.k + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include +#include + + +static const guint8 kate_header_0x80[64] = { + 0x80, 0x6b, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00, 0x20, /* .kate...... ... */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, /* ................ */ + 0x65, 0x6e, 0x5f, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* en_GB........... */ + 0x6e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* none............ */ +}; + +static const guint8 kate_header_0x81[53] = { + 0x81, 0x6b, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x6c, 0x69, 0x62, /* .kate........lib */ + 0x6b, 0x61, 0x74, 0x65, 0x20, 0x30, 0x2e, 0x31, 0x2e, 0x30, 0x20, 0x28, 0x54, 0x69, 0x67, 0x65, /* kate 0.1.0 (Tige */ + 0x72, 0x29, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x54, 0x49, 0x54, 0x4c, 0x45, 0x3d, /* r)........TITLE= */ + 0x54, 0x69, 0x67, 0x65, 0x72, /* Tiger */ +}; +static const guint8 kate_header_0x8x[10] = { + 0x80, 0x6b, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, 0x00 +}; +static const guint8 kate_header_0x88[11] = { + 0x88, 0x6b, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const guint8 kate_header_0x00[45] = { + 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, /* ................ */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x70, 0x6c, 0x61, /* .............pla */ + 0x69, 0x6e, 0x20, 0x6f, 0x6c, 0x64, 0x20, 0x74, 0x65, 0x78, 0x74, 0x08, 0x00 /* in old text.. */ +}; +static const guint8 kate_header_0x01[1] = { + 0x01 +}; +static const guint8 kate_header_0x7f[1] = { + 0x7f +}; + +static const unsigned char kate_spu[] = { + 0x00, 0x1b, /* size */ + 0x00, 0x06, /* commands at offset 6 */ + 0x45, /* first line data - 2 pixels of colors 0 and 1 */ + 0x76, /* first line data - 2 pixels of colors 3 and 2 */ + 0x00, 0x00, /* timestamp */ + 0x00, 0x06, /* link to next command sequence - points back to the current one to mark no more */ + 0x06, 0x00, 0x04, 0x00, 0x05, /* pointers to data */ + 0x05, 0x00, 0x30, 0x04, 0x00, 0x10, 0x02, /* area: 3x1 -> 4x2 */ + 0x04, 0x0f, 0xff, /* alpha: color 0 transparent, all others opaque */ + 0x01, /* show */ + 0xff /* end */ +}; + +/* A lot of these taken from the vorbisdec test */ + +/* For ease of programming we use globals to keep refs for our floating + * src and sink pads we create; otherwise we always have to do get_pad, + * get_peer, and then remove references in every test function */ +static GstPad *mydecsrcpad, *mydecsinkpad; +static GstPad *myencsrcpad, *myencsinkpad; +static GstPad *myparsesrcpad, *myparsesinkpad; +static GstPad *mytagsrcpad, *mytagsinkpad; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstElement * +setup_katedec (void) +{ + GstElement *katedec; + + GST_DEBUG ("setup_katedec"); + katedec = gst_check_setup_element ("katedec"); + mydecsrcpad = gst_check_setup_src_pad (katedec, &srctemplate, NULL); + mydecsinkpad = gst_check_setup_sink_pad (katedec, &sinktemplate, NULL); + gst_pad_set_active (mydecsrcpad, TRUE); + gst_pad_set_active (mydecsinkpad, TRUE); + + return katedec; +} + +static void +cleanup_katedec (GstElement * katedec) +{ + GST_DEBUG ("cleanup_katedec"); + gst_element_set_state (katedec, GST_STATE_NULL); + + gst_pad_set_active (mydecsrcpad, FALSE); + gst_pad_set_active (mydecsinkpad, FALSE); + gst_check_teardown_src_pad (katedec); + gst_check_teardown_sink_pad (katedec); + gst_check_teardown_element (katedec); +} + +static GstElement * +setup_kateenc (void) +{ + GstElement *kateenc; + + GST_DEBUG ("setup_kateenc"); + kateenc = gst_check_setup_element ("kateenc"); + myencsrcpad = gst_check_setup_src_pad (kateenc, &srctemplate, NULL); + myencsinkpad = gst_check_setup_sink_pad (kateenc, &sinktemplate, NULL); + gst_pad_set_active (myencsrcpad, TRUE); + gst_pad_set_active (myencsinkpad, TRUE); + + return kateenc; +} + +static void +cleanup_kateenc (GstElement * kateenc) +{ + GST_DEBUG ("cleanup_kateenc"); + gst_element_set_state (kateenc, GST_STATE_NULL); + + gst_pad_set_active (myencsrcpad, FALSE); + gst_pad_set_active (myencsinkpad, FALSE); + gst_check_teardown_src_pad (kateenc); + gst_check_teardown_sink_pad (kateenc); + gst_check_teardown_element (kateenc); +} + +static GstElement * +setup_kateparse (void) +{ + GstElement *kateparse; + + GST_DEBUG ("setup_kateparse"); + kateparse = gst_check_setup_element ("kateparse"); + myparsesrcpad = gst_check_setup_src_pad (kateparse, &srctemplate, NULL); + myparsesinkpad = gst_check_setup_sink_pad (kateparse, &sinktemplate, NULL); + gst_pad_set_active (myparsesrcpad, TRUE); + gst_pad_set_active (myparsesinkpad, TRUE); + + return kateparse; +} + +static void +cleanup_kateparse (GstElement * kateparse) +{ + GST_DEBUG ("cleanup_kateparse"); + gst_element_set_state (kateparse, GST_STATE_NULL); + + gst_pad_set_active (myparsesrcpad, FALSE); + gst_pad_set_active (myparsesinkpad, FALSE); + gst_check_teardown_src_pad (kateparse); + gst_check_teardown_sink_pad (kateparse); + gst_check_teardown_element (kateparse); +} + +static GstElement * +setup_katetag (void) +{ + GstElement *katetag; + + GST_DEBUG ("setup_katetag"); + katetag = gst_check_setup_element ("katetag"); + mytagsrcpad = gst_check_setup_src_pad (katetag, &srctemplate, NULL); + mytagsinkpad = gst_check_setup_sink_pad (katetag, &sinktemplate, NULL); + gst_pad_set_active (mytagsrcpad, TRUE); + gst_pad_set_active (mytagsinkpad, TRUE); + + return katetag; +} + +static void +cleanup_katetag (GstElement * katetag) +{ + GST_DEBUG ("cleanup_katetag"); + gst_element_set_state (katetag, GST_STATE_NULL); + + gst_pad_set_active (mytagsrcpad, FALSE); + gst_pad_set_active (mytagsinkpad, FALSE); + gst_check_teardown_src_pad (katetag); + gst_check_teardown_sink_pad (katetag); + gst_check_teardown_element (katetag); +} + +static void +check_buffers (guint expected, gboolean headers_in_caps) +{ + GstBuffer *outbuffer; + guint i, num_buffers; + const int num_headers = 9; + unsigned char packet_type; + + /* check buffers are the type we expect */ + num_buffers = g_list_length (buffers); + fail_unless (num_buffers >= num_headers + expected); /* at least 9 headers, plus a variable number of data packets */ + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + fail_if (GST_BUFFER_SIZE (outbuffer) == 0); + + if (i < num_headers) { + /* different headers packets */ + packet_type = (0x80 | i); + fail_unless (GST_BUFFER_DATA (outbuffer)[0] == packet_type); + /* headers could be in caps, so would have an extra ref */ + } else if (i == num_buffers - 1) { + /* eos data packet */ + packet_type = 0x7f; + fail_unless (GST_BUFFER_DATA (outbuffer)[0] == packet_type); + } else { + /* data packet */ + packet_type = 0; + fail_unless (GST_BUFFER_DATA (outbuffer)[0] >= 0 + && GST_BUFFER_DATA (outbuffer)[0] < 0x7f); + } + + buffers = g_list_remove (buffers, outbuffer); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } +} + +GST_START_TEST (test_kate_typefind) +{ + GstTypeFindProbability prob; + const gchar *type; + GstBuffer *buf; + GstCaps *caps = NULL; + + buf = gst_buffer_new (); + GST_BUFFER_DATA (buf) = (guint8 *) kate_header_0x80; + GST_BUFFER_SIZE (buf) = sizeof (kate_header_0x80); + GST_BUFFER_OFFSET (buf) = 0; + + caps = gst_type_find_helper_for_buffer (NULL, buf, &prob); + fail_unless (caps != NULL); + GST_LOG ("Found type: %" GST_PTR_FORMAT, caps); + + type = gst_structure_get_name (gst_caps_get_structure (caps, 0)); + fail_unless_equals_string (type, "application/x-kate"); + fail_unless (prob > GST_TYPE_FIND_MINIMUM && prob <= GST_TYPE_FIND_MAXIMUM); + + gst_buffer_unref (buf); + gst_caps_unref (caps); +} + +GST_END_TEST; + +GST_START_TEST (test_kate_empty_identification_header) +{ + GstElement *katedec; + GstBuffer *inbuffer; + GstBus *bus; + + katedec = setup_katedec (); + bus = gst_bus_new (); + + fail_unless (gst_element_set_state (katedec, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (0); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + + /* set a bus here so we avoid getting state change messages */ + gst_element_set_bus (katedec, bus); + + fail_unless_equals_int (gst_pad_push (mydecsrcpad, inbuffer), GST_FLOW_ERROR); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 0); + + gst_element_set_bus (katedec, NULL); + + /* cleanup */ + gst_object_unref (GST_OBJECT (bus)); + cleanup_katedec (katedec); +} + +GST_END_TEST; + +/* FIXME: also tests comment header */ +GST_START_TEST (test_kate_identification_header) +{ + GstElement *katedec; + GstBuffer *inbuffer; + GstBus *bus; + GstMessage *message; + GstTagList *tag_list; + gchar *language; + gchar *title; + + katedec = setup_katedec (); + fail_unless (gst_element_set_state (katedec, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + inbuffer = gst_buffer_new_and_alloc (sizeof (kate_header_0x80)); + memcpy (GST_BUFFER_DATA (inbuffer), kate_header_0x80, + sizeof (kate_header_0x80)); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_ref (inbuffer); + + gst_element_set_bus (katedec, bus); + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mydecsrcpad, inbuffer) == GST_FLOW_OK); + /* ... and nothing ends up on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_unref (inbuffer); + fail_unless (g_list_length (buffers) == 0); + + inbuffer = gst_buffer_new_and_alloc (sizeof (kate_header_0x81)); + memcpy (GST_BUFFER_DATA (inbuffer), kate_header_0x81, + sizeof (kate_header_0x81)); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_ref (inbuffer); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mydecsrcpad, inbuffer) == GST_FLOW_OK); + /* ... and nothing ends up on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_unref (inbuffer); + fail_unless (g_list_length (buffers) == 0); + /* there's a tag message waiting */ + fail_if ((message = gst_bus_pop (bus)) == NULL); + gst_message_parse_tag (message, &tag_list); + fail_unless_equals_int (gst_tag_list_get_tag_size (tag_list, + GST_TAG_LANGUAGE_CODE), 1); + fail_unless (gst_tag_list_get_string (tag_list, GST_TAG_LANGUAGE_CODE, + &language)); + fail_unless_equals_string (language, "en_GB"); + g_free (language); + fail_unless_equals_int (gst_tag_list_get_tag_size (tag_list, "title"), 1); + fail_unless (gst_tag_list_get_string (tag_list, GST_TAG_TITLE, &title)); + fail_unless_equals_string (title, "Tiger"); + g_free (title); + gst_tag_list_free (tag_list); + gst_message_unref (message); + + /* cleanup */ + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (katedec, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_katedec (katedec); +} + +GST_END_TEST; + +GST_START_TEST (test_kate_encode_nothing) +{ + GstElement *kateenc; + + kateenc = setup_kateenc (); + fail_unless (gst_element_set_state (kateenc, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + fail_unless (gst_pad_push_event (myencsrcpad, gst_event_new_eos ()) == TRUE); + + fail_unless (gst_element_set_state (kateenc, + GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS, + "could not set to ready"); + + /* cleanup */ + cleanup_kateenc (kateenc); +} + +GST_END_TEST; + +GST_START_TEST (test_kate_encode_empty) +{ + GstElement *kateenc; + GstBuffer *inbuffer; + GstBus *bus; + GstCaps *caps; + + kateenc = setup_kateenc (); + fail_unless (gst_element_set_state (kateenc, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + inbuffer = gst_buffer_new_and_alloc (0); + GST_BUFFER_TIMESTAMP (inbuffer) = GST_BUFFER_OFFSET (inbuffer) = + 1 * GST_SECOND; + GST_BUFFER_DURATION (inbuffer) = 5 * GST_SECOND; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_ref (inbuffer); + + caps = gst_caps_from_string ("text/plain"); + fail_unless (caps != NULL); + gst_buffer_set_caps (inbuffer, caps); + gst_caps_unref (caps); + + gst_element_set_bus (kateenc, bus); + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (myencsrcpad, inbuffer) == GST_FLOW_ERROR); + + fail_unless (gst_pad_push_event (myencsrcpad, gst_event_new_eos ()) == TRUE); + + fail_unless (gst_element_set_state (kateenc, + GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS, + "could not set to ready"); + + gst_element_set_bus (kateenc, NULL); + + /* cleanup */ + gst_object_unref (GST_OBJECT (bus)); + cleanup_kateenc (kateenc); +} + +GST_END_TEST; + +GST_START_TEST (test_kate_encode_simple) +{ + GstElement *kateenc; + GstBuffer *inbuffer; + GstBus *bus; + const gchar *test_string = ""; + GstCaps *caps; + + kateenc = setup_kateenc (); + fail_unless (gst_element_set_state (kateenc, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + inbuffer = gst_buffer_new_and_alloc (strlen (test_string) + 1); + memcpy (GST_BUFFER_DATA (inbuffer), test_string, strlen (test_string) + 1); + GST_BUFFER_TIMESTAMP (inbuffer) = GST_BUFFER_OFFSET (inbuffer) = + 1 * GST_SECOND; + GST_BUFFER_DURATION (inbuffer) = 5 * GST_SECOND; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_ref (inbuffer); + + caps = gst_caps_from_string ("text/plain"); + fail_unless (caps != NULL); + gst_buffer_set_caps (inbuffer, caps); + gst_caps_unref (caps); + + gst_element_set_bus (kateenc, bus); + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (myencsrcpad, inbuffer) == GST_FLOW_OK); + /* ... and nothing ends up on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_unref (inbuffer); + fail_unless (gst_pad_push_event (myencsrcpad, gst_event_new_eos ()) == TRUE); + + fail_unless (gst_element_set_state (kateenc, + GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS, + "could not set to ready"); + + /* at least one data packet and one EOS packet should have been emitted */ + check_buffers (1 + 1, FALSE); + + /* cleanup */ + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (kateenc, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_kateenc (kateenc); + g_list_free (buffers); +} + +GST_END_TEST; + +GST_START_TEST (test_kate_encode_spu) +{ + GstElement *kateenc; + GstBuffer *inbuffer; + GstBus *bus; + GstCaps *caps; + + kateenc = setup_kateenc (); + fail_unless (gst_element_set_state (kateenc, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + inbuffer = gst_buffer_new_and_alloc (sizeof (kate_spu)); + memcpy (GST_BUFFER_DATA (inbuffer), kate_spu, sizeof (kate_spu)); + GST_BUFFER_TIMESTAMP (inbuffer) = GST_BUFFER_OFFSET (inbuffer) = + 1 * GST_SECOND; + GST_BUFFER_DURATION (inbuffer) = 5 * GST_SECOND; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_ref (inbuffer); + + caps = gst_caps_from_string ("video/x-dvd-subpicture"); + fail_unless (caps != NULL); + gst_buffer_set_caps (inbuffer, caps); + gst_caps_unref (caps); + + gst_element_set_bus (kateenc, bus); + /* pushing gives away my reference ... */ + fail_unless_equals_int (gst_pad_push (myencsrcpad, inbuffer), GST_FLOW_OK); + /* ... and nothing ends up on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_unref (inbuffer); + fail_unless (gst_pad_push_event (myencsrcpad, gst_event_new_eos ()) == TRUE); + + fail_unless (gst_element_set_state (kateenc, + GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS, + "could not set to ready"); + + /* at least one data packet and one EOS packet should have been emitted */ + check_buffers (2, FALSE); + + /* cleanup */ + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (kateenc, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_kateenc (kateenc); + g_list_free (buffers); +} + +GST_END_TEST; + +GST_START_TEST (test_kate_encode_keepalives) +{ + GstElement *kateenc; + GstBus *bus; + guint i, round; + enum + { n_keepalives = 1000 }; + static const struct + { + gdouble keepalive_min_time; + gint packets; + } cfg[3] = { + { + 0.5, n_keepalives}, { + 2.0, n_keepalives / 2}, { + 5.0, n_keepalives / 5},}; + + for (round = 0; round < 3; ++round) { + kateenc = setup_kateenc (); + fail_unless (gst_element_set_state (kateenc, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + gst_element_set_bus (kateenc, bus); + + g_object_set (kateenc, "keepalive-min-time", cfg[round].keepalive_min_time, + NULL); + + /* the second one here should not emit a keepalive since the time since last packet + is less than the keepalive delay */ + for (i = 1; i <= n_keepalives; ++i) { + gint64 t = i * GST_SECOND; + fail_unless (gst_pad_push_event (myencsrcpad, + gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME, t, -1, + 0)) == TRUE); + } + + fail_unless (gst_pad_push_event (myencsrcpad, + gst_event_new_eos ()) == TRUE); + + fail_unless (gst_element_set_state (kateenc, + GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS, + "could not set to ready"); + + /* at least a number data packet and an EOS packet should have been emitted */ + check_buffers (cfg[round].packets + 1, FALSE); + + /* cleanup */ + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (kateenc, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_kateenc (kateenc); + g_list_free (buffers); + } +} + +GST_END_TEST; + +static void +test_kate_send_headers (GstPad * pad) +{ + GstBuffer *inbuffer; + int i; + + /* push headers */ + inbuffer = gst_buffer_new (); + GST_BUFFER_DATA (inbuffer) = (guint8 *) kate_header_0x80; + GST_BUFFER_SIZE (inbuffer) = sizeof (kate_header_0x80); + GST_BUFFER_OFFSET (inbuffer) = GST_BUFFER_OFFSET_END (inbuffer) = 0; + fail_unless_equals_int (gst_pad_push (pad, inbuffer), GST_FLOW_OK); + + inbuffer = gst_buffer_new (); + GST_BUFFER_DATA (inbuffer) = (guint8 *) kate_header_0x81; + GST_BUFFER_SIZE (inbuffer) = sizeof (kate_header_0x81); + GST_BUFFER_OFFSET (inbuffer) = GST_BUFFER_OFFSET_END (inbuffer) = 0; + fail_unless_equals_int (gst_pad_push (pad, inbuffer), GST_FLOW_OK); + + for (i = 2; i < 8; ++i) { + inbuffer = gst_buffer_new_and_alloc (sizeof (kate_header_0x8x)); + memcpy (GST_BUFFER_DATA (inbuffer), (guint8 *) kate_header_0x8x, + sizeof (kate_header_0x8x)); + GST_BUFFER_DATA (inbuffer)[0] = 0x80 | i; + GST_BUFFER_OFFSET (inbuffer) = GST_BUFFER_OFFSET_END (inbuffer) = 0; + fail_unless_equals_int (gst_pad_push (pad, inbuffer), GST_FLOW_OK); + } + + inbuffer = gst_buffer_new (); + GST_BUFFER_DATA (inbuffer) = (guint8 *) kate_header_0x88; + GST_BUFFER_SIZE (inbuffer) = sizeof (kate_header_0x88); + GST_BUFFER_OFFSET (inbuffer) = GST_BUFFER_OFFSET_END (inbuffer) = 0; + fail_unless_equals_int (gst_pad_push (pad, inbuffer), GST_FLOW_OK); +} + +GST_START_TEST (test_kate_parse) +{ + GstElement *kateparse; + GstBuffer *inbuffer; + GstBus *bus; + + kateparse = setup_kateparse (); + fail_unless (gst_element_set_state (kateparse, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + gst_element_set_bus (kateparse, bus); + + test_kate_send_headers (myparsesrcpad); + + /* push a text packet */ + inbuffer = gst_buffer_new (); + GST_BUFFER_DATA (inbuffer) = (guint8 *) kate_header_0x00; + GST_BUFFER_SIZE (inbuffer) = sizeof (kate_header_0x00); + GST_BUFFER_TIMESTAMP (inbuffer) = GST_BUFFER_OFFSET (inbuffer) = + 1 * GST_SECOND; + GST_BUFFER_DURATION (inbuffer) = 5 * GST_SECOND; + GST_BUFFER_OFFSET_END (inbuffer) = (GST_BUFFER_TIMESTAMP (inbuffer) << 32); /* granpos */ + fail_unless_equals_int (gst_pad_push (myparsesrcpad, inbuffer), GST_FLOW_OK); + + /* push a eos packet */ + inbuffer = gst_buffer_new (); + GST_BUFFER_DATA (inbuffer) = (guint8 *) kate_header_0x7f; + GST_BUFFER_SIZE (inbuffer) = sizeof (kate_header_0x7f); + GST_BUFFER_TIMESTAMP (inbuffer) = GST_BUFFER_OFFSET (inbuffer) = + 6 * GST_SECOND; + GST_BUFFER_DURATION (inbuffer) = 0; + GST_BUFFER_OFFSET_END (inbuffer) = (GST_BUFFER_TIMESTAMP (inbuffer) << 32); /* granpos */ + fail_unless_equals_int (gst_pad_push (myparsesrcpad, inbuffer), GST_FLOW_OK); + + /* signal eos */ + fail_unless (gst_pad_push_event (myparsesrcpad, + gst_event_new_eos ()) == TRUE); + + fail_unless (gst_element_set_state (kateparse, + GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS, + "could not set to ready"); + + /* at least one data packet and one EOS packet should have been emitted */ + check_buffers (2, TRUE); + + /* cleanup */ + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (kateparse, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_kateparse (kateparse); + g_list_free (buffers); +} + +GST_END_TEST; + +GST_START_TEST (test_kate_tag_passthrough) +{ + GstElement *katetag; + GstBus *bus; + GstBuffer *outbuffer; + GList *list; + + katetag = setup_katetag (); + fail_unless (gst_element_set_state (katetag, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + gst_element_set_bus (katetag, bus); + + test_kate_send_headers (mytagsrcpad); + + /* signal eos */ + fail_unless (gst_pad_push_event (mytagsrcpad, gst_event_new_eos ()) == TRUE); + + fail_unless (gst_element_set_state (katetag, + GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS, + "could not set to ready"); + + /* get the first buffer and check language/category */ + fail_unless (g_list_length (buffers) >= 2); /* ID header, Vorbis comments header */ + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + + /* check identification header is unchanged */ + list = g_list_nth (buffers, 0); + fail_unless (list != NULL); + outbuffer = list->data; + fail_unless_equals_int (GST_BUFFER_SIZE (outbuffer), + sizeof (kate_header_0x80)); + fail_unless_equals_int (memcmp (GST_BUFFER_DATA (outbuffer), kate_header_0x80, + sizeof (kate_header_0x80)), 0); + + /* check comment header is unchanged */ + list = g_list_nth (buffers, 1); + fail_unless (list != NULL); + outbuffer = list->data; + fail_unless_equals_int (GST_BUFFER_SIZE (outbuffer), + sizeof (kate_header_0x81)); + fail_unless_equals_int (memcmp (GST_BUFFER_DATA (outbuffer), kate_header_0x81, + sizeof (kate_header_0x81)), 0); + + /* all headers should have been emitted, but no particular packets */ + check_buffers (0, TRUE); + + /* cleanup */ + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (katetag, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_katetag (katetag); + g_list_free (buffers); +} + +GST_END_TEST; + +GST_START_TEST (test_kate_tag) +{ + GstElement *katetag; + GstBus *bus; + GstBuffer *outbuffer; + + katetag = setup_katetag (); + fail_unless (gst_element_set_state (katetag, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + bus = gst_bus_new (); + + gst_element_set_bus (katetag, bus); + + g_object_set (katetag, "language", "cy", NULL); + g_object_set (katetag, "category", "subtitles", NULL); + + test_kate_send_headers (mytagsrcpad); + + /* signal eos */ + fail_unless (gst_pad_push_event (mytagsrcpad, gst_event_new_eos ()) == TRUE); + + fail_unless (gst_element_set_state (katetag, + GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS, + "could not set to ready"); + + /* get the first buffer and check language/category */ + fail_unless (g_list_length (buffers) >= 1); + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + fail_if (GST_BUFFER_SIZE (outbuffer) != 64); + fail_if (strcmp ((const char *) GST_BUFFER_DATA (outbuffer) + 32, "cy")); + fail_if (strcmp ((const char *) GST_BUFFER_DATA (outbuffer) + 48, + "subtitles")); + + /* all headers should have been emitted, but no particular packets */ + check_buffers (0, TRUE); + + /* cleanup */ + gst_bus_set_flushing (bus, TRUE); + gst_element_set_bus (katetag, NULL); + gst_object_unref (GST_OBJECT (bus)); + cleanup_katetag (katetag); + g_list_free (buffers); +} + +GST_END_TEST; + +Suite * +kate_suite (void) +{ + Suite *s = suite_create ("kate"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + +#define X if (0) + tcase_add_test (tc_chain, test_kate_typefind); + tcase_add_test (tc_chain, test_kate_empty_identification_header); + tcase_add_test (tc_chain, test_kate_identification_header); + tcase_add_test (tc_chain, test_kate_encode_nothing); + tcase_add_test (tc_chain, test_kate_encode_empty); + tcase_add_test (tc_chain, test_kate_encode_simple); + tcase_add_test (tc_chain, test_kate_encode_spu); + tcase_add_test (tc_chain, test_kate_encode_keepalives); + tcase_add_test (tc_chain, test_kate_parse); + tcase_add_test (tc_chain, test_kate_tag_passthrough); + tcase_add_test (tc_chain, test_kate_tag); +#undef X + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = kate_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +}