summaryrefslogtreecommitdiff
path: root/src/spu_dec
diff options
context:
space:
mode:
Diffstat (limited to 'src/spu_dec')
-rw-r--r--src/spu_dec/Makefile.am42
-rw-r--r--src/spu_dec/cc_decoder.c1517
-rw-r--r--src/spu_dec/cc_decoder.h80
-rw-r--r--src/spu_dec/cmml_decoder.c525
-rw-r--r--src/spu_dec/nav_read.c1
-rw-r--r--src/spu_dec/spu_decoder.c386
-rw-r--r--src/spu_dec/spudec.c1034
-rw-r--r--src/spu_dec/spudec.h146
-rw-r--r--src/spu_dec/spudvb_decoder.c1218
-rw-r--r--src/spu_dec/spuhdmv_decoder.c1072
-rw-r--r--src/spu_dec/sputext_decoder.c1211
-rw-r--r--src/spu_dec/sputext_demuxer.c1452
-rw-r--r--src/spu_dec/xine_cc_decoder.c361
13 files changed, 9045 insertions, 0 deletions
diff --git a/src/spu_dec/Makefile.am b/src/spu_dec/Makefile.am
new file mode 100644
index 000000000..85aedf9e9
--- /dev/null
+++ b/src/spu_dec/Makefile.am
@@ -0,0 +1,42 @@
+include $(top_srcdir)/misc/Makefile.quiet
+include $(top_builddir)/misc/Makefile.plugins
+include $(top_srcdir)/misc/Makefile.common
+
+AM_CFLAGS = $(DEFAULT_OCFLAGS) $(VISIBILITY_FLAG)
+AM_LDFLAGS = $(xineplug_ldflags)
+
+xineplug_LTLIBRARIES = \
+ xineplug_decode_spucc.la \
+ xineplug_decode_spucmml.la \
+ xineplug_decode_spu.la \
+ xineplug_decode_spudvb.la \
+ xineplug_decode_spuhdmv.la \
+ xineplug_sputext.la
+
+xineplug_decode_spucc_la_SOURCES = cc_decoder.c cc_decoder.h xine_cc_decoder.c
+xineplug_decode_spucc_la_LIBADD = $(XINE_LIB)
+xineplug_decode_spucc_la_CFLAGS = $(AM_CFLAGS) -fno-strict-aliasing
+
+xineplug_decode_spucmml_la_SOURCES = cmml_decoder.c
+xineplug_decode_spucmml_la_LIBADD = $(XINE_LIB) $(LTLIBINTL)
+
+if WITH_EXTERNAL_DVDNAV
+external_dvdnav_libs = $(DVDNAV_LIBS)
+internal_dvdnav_sources =
+else
+external_dvdnav_libs =
+internal_dvdnav_sources = nav_read.c
+endif
+
+xineplug_decode_spu_la_SOURCES = $(internal_dvdnav_sources) spudec.c spudec.h spu_decoder.c
+xineplug_decode_spu_la_LIBADD = $(XINE_LIB) $(external_dvdnav_libs) $(PTHREAD_LIBS) $(LTLIBINTL)
+xineplug_decode_spu_la_CFLAGS = $(AM_CFLAGS) $(DVDNAV_CFLAGS) -I$(top_srcdir)/src/input/libdvdnav
+
+xineplug_decode_spudvb_la_SOURCES = spudvb_decoder.c
+xineplug_decode_spudvb_la_LIBADD = $(XINE_LIB) $(PTHREAD_LIBS) $(LTLIBINTL)
+
+xineplug_decode_spuhdmv_la_SOURCES = spuhdmv_decoder.c
+xineplug_decode_spuhdmv_la_LIBADD = $(XINE_LIB) $(PTHREAD_LIBS) $(LTLIBINTL)
+
+xineplug_sputext_la_SOURCES = sputext_demuxer.c sputext_decoder.c
+xineplug_sputext_la_LIBADD = $(XINE_LIB) $(LTLIBINTL)
diff --git a/src/spu_dec/cc_decoder.c b/src/spu_dec/cc_decoder.c
new file mode 100644
index 000000000..d60d2cf03
--- /dev/null
+++ b/src/spu_dec/cc_decoder.c
@@ -0,0 +1,1517 @@
+/*
+ * Copyright (C) 2000-2003 the xine project
+ *
+ * Copyright (C) Christian Vogler
+ * cvogler@gradient.cis.upenn.edu - December 2001
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * stuff needed to provide closed captioning decoding and display
+ *
+ * Some small bits and pieces of the EIA-608 captioning decoder were
+ * adapted from CCDecoder 0.9.1 by Mike Baker. The latest version is
+ * available at http://sourceforge.net/projects/ccdecoder/.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/time.h>
+
+#include <inttypes.h>
+
+#include <xine/xine_internal.h>
+#include <xine/video_out.h>
+#include <xine/xineutils.h>
+#include <xine/osd.h>
+#include "cc_decoder.h"
+#include <xine/osd.h>
+
+/*
+#define LOG_DEBUG 3
+*/
+
+/* at 29.97 fps, each NTSC frame takes 3003 metronom ticks on the average. */
+#define NTSC_FRAME_DURATION 3003
+
+#define CC_ROWS 15
+#define CC_COLUMNS 32
+#define CC_CHANNELS 2
+
+/* 1 is the caption background color index in the OSD palettes. */
+#define CAP_BG_COL 1
+
+/* number of text colors specified by EIA-608 standard */
+#define NUM_FG_COL 7
+
+#ifndef WIN32
+/* colors specified by the EIA 608 standard */
+enum { WHITE, GREEN, BLUE, CYAN, RED, YELLOW, MAGENTA, BLACK, TRANSPARENT };
+#else
+/* colors specified by the EIA 608 standard */
+enum { WHITE, GREEN, BLUE, CYAN, RED, YELLOW, MAGENTA, BLACK };
+#endif
+
+
+
+/* color mapping to OSD text color indices */
+static const int text_colormap[NUM_FG_COL] = {
+ OSD_TEXT1, OSD_TEXT2, OSD_TEXT3, OSD_TEXT4, OSD_TEXT5, OSD_TEXT6, OSD_TEXT7
+};
+
+
+/* -------------------- caption text colors -----------------------------*/
+/* FIXME: The colors look fine on an XShm display, but they look *terrible*
+ with the Xv display on the NVidia driver on a GeForce 3. The colors bleed
+ into each other more than I'd expect from the downsampling into YUV
+ colorspace.
+ At this moment, it looks like a problem in the Xv YUV blending functions.
+*/
+typedef struct colorinfo_s {
+ clut_t bgcol; /* text background color */
+ clut_t bordercol; /* text border color */
+ clut_t textcol; /* text color */
+} colorinfo_t;
+
+
+static const colorinfo_t cc_text_trans[NUM_FG_COL] = {
+ /* white, black border, translucid */
+ {
+ CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xff, 0x80, 0x80)
+ },
+
+ /* green, black border, translucid */
+ {
+ CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x90, 0x22, 0x35)
+ },
+
+ /* blue, black border, translucid */
+ {
+ CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x29, 0x6e, 0xff)
+ },
+
+ /* cyan, black border, translucid */
+ {
+ CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xaa, 0x10, 0xa6)
+ },
+
+ /* red, black border, translucid */
+ {
+ CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x52, 0xf0, 0x5a)
+ },
+
+ /* yellow, black border, translucid */
+ {
+ CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xd4, 0x92, 0x10)
+ },
+
+ /* magenta, black border, translucid */
+ {
+ CLUT_Y_CR_CB_INIT(0x80, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x6b, 0xde, 0xca)
+ }
+};
+
+static const colorinfo_t cc_text_solid[NUM_FG_COL] = {
+ /* white, black border, solid */
+ {
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xff, 0x80, 0x80)
+ },
+
+ /* green, black border, solid */
+ {
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x90, 0x22, 0x35)
+ },
+
+ /* blue, black border, solid */
+ {
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x29, 0x6e, 0xff)
+ },
+
+ /* cyan, black border, solid */
+ {
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xaa, 0x10, 0xa6)
+ },
+
+ /* red, black border, solid */
+ {
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x52, 0xf0, 0x5a)
+ },
+
+ /* yellow, black border, solid */
+ {
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xd4, 0x92, 0x10)
+ },
+
+ /* magenta, black border, solid */
+ {
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x6b, 0xde, 0xca)
+ }
+};
+
+
+static const uint8_t cc_text_trans_alpha[TEXT_PALETTE_SIZE] = {
+ 0, 8, 9, 10, 11, 12, 15, 15, 15, 15, 15
+};
+
+static const uint8_t cc_text_solid_alpha[TEXT_PALETTE_SIZE] = {
+ 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15
+};
+
+
+static const colorinfo_t *const cc_text_palettes[NUM_CC_PALETTES] = {
+ cc_text_trans,
+ cc_text_solid
+};
+
+static const uint8_t *const cc_alpha_palettes[NUM_CC_PALETTES] = {
+ cc_text_trans_alpha,
+ cc_text_solid_alpha
+};
+
+/* --------------------- misc. EIA 608 definitions -------------------*/
+
+#define TRANSP_SPACE 0x19 /* code for transparent space, essentially
+ arbitrary */
+
+/* mapping from PAC row code to actual CC row */
+static const int rowdata[] = {10, -1, 0, 1, 2, 3, 11, 12, 13, 14, 4, 5, 6,
+ 7, 8, 9};
+/* FIXME: do real ™ (U+2122) */
+/* Code 182 must be mapped as a musical note ('♪', U+266A) in the caption font */
+static const char specialchar[] = {
+ 174 /* ® */, 176 /* ° */, 189 /* ½ */, 191 /* ¿ */,
+ 'T' /* ™ */, 162 /* ¢ */, 163 /* £ */, 182 /* ¶ => ♪ */,
+ 224 /* à */, TRANSP_SPACE,232 /* è */, 226 /* â */,
+ 234 /* ê */, 238 /* î */, 244 /* ô */, 251 /* û */
+};
+
+/**
+ * @brief Character translation table
+ *
+ * EIA 608 codes are not all the same as ASCII
+ *
+ * The code to produce the characters table would be the following:
+ *
+ * static void build_char_table(void)
+ * {
+ * int i;
+ * // first the normal ASCII codes
+ * for (i = 0; i < 128; i++)
+ * chartbl[i] = (char) i;
+ * // now the special codes
+ * chartbl[0x2a] = 225; // á
+ * chartbl[0x5c] = 233; // é
+ * chartbl[0x5e] = 237; // í
+ * chartbl[0x5f] = 243; // ó
+ * chartbl[0x60] = 250; // ú
+ * chartbl[0x7b] = 231; // ç
+ * chartbl[0x7c] = 247; // ÷
+ * chartbl[0x7d] = 209; // Ñ
+ * chartbl[0x7e] = 241; // ñ
+ * chartbl[0x7f] = 164; // ¤ FIXME: should be a solid block ('█'; U+2588)
+ * }
+ *
+ */
+static const int chartbl[128] = {
+ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
+ '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
+ '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
+ '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
+ '\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
+ '\x28', '\x29', '\xe1', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
+ '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
+ '\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
+ '\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47',
+ '\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f',
+ '\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57',
+ '\x58', '\x59', '\x5a', '\x5b', '\xe9', '\x5d', '\xed', '\xf3',
+ '\xfa', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
+ '\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f',
+ '\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77',
+ '\x78', '\x79', '\x7a', '\xe7', '\xf7', '\xd1', '\xf1', '\xa4'
+};
+
+/**
+ * @brief Parity table for packets
+ *
+ * CC codes use odd parity for error detection, since they originally were
+ * transmitted via noisy video signals.
+ *
+ * The code to produce the parity table would be the following:
+ *
+ * static int parity(uint8_t byte)
+ * {
+ * int i;
+ * int ones = 0;
+ *
+ * for (i = 0; i < 7; i++) {
+ * if (byte & (1 << i))
+ * ones++;
+ * }
+ *
+ * return ones & 1;
+ * }
+ *
+ * static void build_parity_table(void)
+ * {
+ * uint8_t byte;
+ * int parity_v;
+ * for (byte = 0; byte <= 127; byte++) {
+ * parity_v = parity(byte);
+ * // CC uses odd parity (i.e., # of 1's in byte is odd.)
+ * parity_table[byte] = parity_v;
+ * parity_table[byte | 0x80] = !parity_v;
+ * }
+ * }
+ */
+static const int parity_table[256] = {
+ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0
+};
+
+/*---------------- decoder data structures -----------------------*/
+
+/* CC renderer */
+struct cc_renderer_s {
+ int video_width; /* video dimensions */
+ int video_height;
+
+ int x; /* coordinates of the captioning area */
+ int y;
+ int width;
+ int height;
+ int max_char_height; /* captioning font properties */
+ int max_char_width;
+
+ osd_renderer_t *osd_renderer; /* active OSD renderer */
+ osd_object_t *cap_display; /* caption display object */
+ int displayed; /* true when caption currently is displayed */
+
+ /* the next variable is a hack: hiding a caption with vpts 0 doesn't seem
+ to work if the caption has been registered in the SPU event queue, but
+ not yet displayed. So we remember the vpts of the show event, and use
+ that as the vpts of the hide event upon an osd free.
+ */
+/*FIXME: bug in OSD or SPU?*/
+ int64_t display_vpts; /* vpts of currently displayed caption */
+
+ /* this variable is an even worse hack: in some rare cases, the pts
+ information on the DVD gets out of sync with the caption information.
+ If this happens, the vpts of a hide caption event can actually be
+ slightly higher than the vpts of the following show caption event.
+ For this reason, we remember the vpts of the hide event and force
+ the next show event's vpts to be at least equal to the hide event's
+ vpts.
+ */
+ int64_t last_hide_vpts;
+
+ /* caption palette and alpha channel */
+ uint32_t cc_palette[OVL_PALETTE_SIZE];
+ uint8_t cc_trans[OVL_PALETTE_SIZE];
+
+ metronom_t *metronom; /* the active xine metronom */
+
+ cc_state_t *cc_state; /* captioning configuration */
+};
+
+
+/* CC attribute */
+typedef struct cc_attribute_s {
+ uint8_t italic;
+ uint8_t underline;
+ uint8_t foreground;
+ uint8_t background;
+} cc_attribute_t;
+
+/* CC character cell */
+typedef struct cc_char_cell_s {
+ uint8_t c; /* character code, not the same as ASCII */
+ cc_attribute_t attributes; /* attributes of this character, if changed */
+ /* here */
+ int midrow_attr; /* true if this cell changes an attribute */
+} cc_char_cell_t;
+
+/* a single row in the closed captioning memory */
+typedef struct cc_row_s {
+ cc_char_cell_t cells[CC_COLUMNS];
+ int pos; /* position of the cursor */
+ int num_chars; /* how many characters in the row are data */
+ int attr_chg; /* true if midrow attr. change at cursor pos */
+ int pac_attr_chg; /* true if attribute has changed via PAC */
+ cc_attribute_t pac_attr; /* PAC attr. that hasn't been applied yet */
+} cc_row_t;
+
+/* closed captioning memory for a single channel */
+typedef struct cc_buffer_s {
+ cc_row_t rows[CC_ROWS];
+ int rowpos; /* row cursor position */
+} cc_buffer_t;
+
+/* captioning memory for all channels */
+typedef struct cc_memory_s {
+ cc_buffer_t channel[CC_CHANNELS];
+ int channel_no; /* currently active channel */
+} cc_memory_t;
+
+/* The closed captioning decoder data structure */
+struct cc_decoder_s {
+ /* CC decoder buffer - one onscreen, one offscreen */
+ cc_memory_t buffer[2];
+ /* onscreen, offscreen buffer ptrs */
+ cc_memory_t *on_buf;
+ cc_memory_t *off_buf;
+ /* which buffer is active for receiving data */
+ cc_memory_t **active;
+
+ /* for logging and debugging purposes, captions are assigned increasing */
+ /* unique ids. */
+ uint32_t capid;
+
+ /* the last captioning code seen (control codes are often sent twice
+ in a row, but should be processed only once) */
+ uint32_t lastcode;
+
+ /* The PTS and SCR at which the captioning chunk started */
+ int64_t pts;
+ /* holds the NTSC frame offset to last known pts/scr */
+ uint32_t f_offset;
+
+ /* active OSD renderer */
+ osd_renderer_t *renderer;
+ /* true when caption currently is displayed */
+ int displayed;
+
+ /* configuration and intrinsics of CC decoder */
+ cc_state_t *cc_state;
+
+ metronom_t *metronom;
+};
+
+
+/*---------------- general utility functions ---------------------*/
+
+static void get_font_metrics(osd_renderer_t *renderer,
+ const char *fontname, int font_size,
+ int *maxw, int *maxh)
+{
+ int c;
+ osd_object_t *testc = renderer->new_object(renderer, 640, 480);
+
+ *maxw = 0;
+ *maxh = 0;
+
+ renderer->set_font(testc, (char *) fontname, font_size);
+ renderer->set_encoding(testc, "iso-8859-1");
+ for (c = 32; c < 256; c++) {
+ int tw, th;
+ const char buf[2] = { c, '\0' };
+
+ renderer->get_text_size(testc, buf, &tw, &th);
+ *maxw = MAX(*maxw, tw);
+ *maxh = MAX(*maxh, th);
+ }
+ renderer->free_object(testc);
+}
+
+
+static int good_parity(uint16_t data)
+{
+ int ret = parity_table[data & 0xff] && parity_table[(data & 0xff00) >> 8];
+ if (! ret)
+ printf("Bad parity in EIA-608 data (%x)\n", data);
+ return ret;
+}
+
+
+
+
+static clut_t interpolate_color(clut_t src, clut_t dest, int steps,
+ int current_step)
+{
+ int diff_y = ((int) dest.y) - ((int) src.y);
+ int diff_cr = ((int) dest.cr) - ((int) src.cr);
+ int diff_cb = ((int) dest.cb) - ((int) src.cb);
+ int res_y = ((int) src.y) + (diff_y * current_step / (steps + 1));
+ int res_cr = ((int) src.cr) + (diff_cr * current_step / (steps + 1));
+ int res_cb = ((int) src.cb) + (diff_cb * current_step / (steps + 1));
+#if __SUNPRO_C
+ /*
+ * Sun's Forte compiler refuses to initialize automatic structure
+ * variable with bitfields, so we use explicit assignments for now.
+ */
+ clut_t res;
+ res.y = res_y;
+ res.cr = res_cr;
+ res.cb = res_cb;
+ res.foo = 0;
+#else
+ clut_t res = CLUT_Y_CR_CB_INIT((uint8_t) res_y, (uint8_t) res_cr,
+ (uint8_t) res_cb);
+#endif
+ return res;
+}
+
+/*----------------- cc_row_t methods --------------------------------*/
+
+static void ccrow_fill_transp(cc_row_t *rowbuf){
+ int i;
+
+#ifdef LOG_DEBUG
+ printf("cc_decoder: ccrow_fill_transp: Filling in %d transparent spaces.\n",
+ rowbuf->pos - rowbuf->num_chars);
+#endif
+ for (i = rowbuf->num_chars; i < rowbuf->pos; i++) {
+ rowbuf->cells[i].c = TRANSP_SPACE;
+ rowbuf->cells[i].midrow_attr = 0;
+ }
+}
+
+
+static int ccrow_find_next_text_part(cc_row_t *this, int pos)
+{
+ while (pos < this->num_chars && this->cells[pos].c == TRANSP_SPACE)
+ pos++;
+ return pos;
+}
+
+
+static int ccrow_find_end_of_text_part(cc_row_t *this, int pos)
+{
+ while (pos < this->num_chars && this->cells[pos].c != TRANSP_SPACE)
+ pos++;
+ return pos;
+}
+
+
+static int ccrow_find_current_attr(cc_row_t *this, int pos)
+{
+ while (pos > 0 && !this->cells[pos].midrow_attr)
+ pos--;
+ return pos;
+}
+
+
+static int ccrow_find_next_attr_change(cc_row_t *this, int pos, int lastpos)
+{
+ pos++;
+ while (pos < lastpos && !this->cells[pos].midrow_attr)
+ pos++;
+ return pos;
+}
+
+
+static void ccrow_set_attributes(cc_renderer_t *renderer, cc_row_t *this,
+ int pos)
+{
+ const cc_attribute_t *attr = &this->cells[pos].attributes;
+ const char *fontname;
+ cc_config_t *cap_info = renderer->cc_state->cc_cfg;
+
+ if (attr->italic)
+ fontname = cap_info->italic_font;
+ else
+ fontname = cap_info->font;
+ renderer->osd_renderer->set_font(renderer->cap_display, (char *) fontname,
+ cap_info->font_size);
+}
+
+
+static void ccrow_render(cc_renderer_t *renderer, cc_row_t *this, int rownum)
+{
+ char buf[CC_COLUMNS + 1];
+ int base_y;
+ int pos = ccrow_find_next_text_part(this, 0);
+ cc_config_t *cap_info = renderer->cc_state->cc_cfg;
+ osd_renderer_t *osd_renderer = renderer->osd_renderer;
+
+ /* find y coordinate of caption */
+ if (cap_info->center) {
+ /* find y-center of the desired row; the next line computes */
+ /* cap_info->height * (rownum + 0.5) / CC_ROWS */
+ /* in integer arithmetic for this purpose. */
+ base_y = (renderer->height * rownum * 100 + renderer->height * 50) /
+ (CC_ROWS * 100);
+ }
+ else
+ base_y = renderer->height * rownum / CC_ROWS;
+
+ /* break down captions into parts separated by transparent space, and */
+ /* center each part individually along the x axis */
+ while (pos < this->num_chars) {
+ int endpos = ccrow_find_end_of_text_part(this, pos);
+ int seg_begin = pos;
+ int seg_end;
+ int i;
+ int text_w = 0, text_h = 0;
+ int x, y;
+ int seg_w, seg_h;
+ int seg_pos[CC_COLUMNS + 1];
+ int seg_attr[CC_COLUMNS];
+ int cumulative_seg_width[CC_COLUMNS + 1];
+ int num_seg = 0;
+ int seg;
+
+ /* break down each part into segments bounded by attribute changes and */
+ /* find text metrics of the parts */
+ seg_pos[0] = seg_begin;
+ cumulative_seg_width[0] = 0;
+ while (seg_begin < endpos) {
+ int attr_pos = ccrow_find_current_attr(this, seg_begin);
+ seg_end = ccrow_find_next_attr_change(this, seg_begin, endpos);
+
+ /* compute text size of segment */
+ for (i = seg_begin; i < seg_end; i++)
+ buf[i - seg_begin] = this->cells[i].c;
+ buf[seg_end - seg_begin] = '\0';
+ ccrow_set_attributes(renderer, this, attr_pos);
+ osd_renderer->get_text_size(renderer->cap_display, buf,
+ &seg_w, &seg_h);
+
+ /* update cumulative segment statistics */
+ text_w += seg_w;
+ text_h += seg_h;
+ seg_pos[num_seg + 1] = seg_end;
+ seg_attr[num_seg] = attr_pos;
+ cumulative_seg_width[num_seg + 1] = text_w;
+ num_seg++;
+
+ seg_begin = seg_end;
+ }
+
+ /* compute x coordinate of part */
+ if (cap_info->center) {
+ int cell_width = renderer->width / CC_COLUMNS;
+ x = (renderer->width * (pos + endpos) / 2) / CC_COLUMNS;
+ x -= text_w / 2;
+ /* clamp x coordinate to nearest character cell */
+ x = ((x + cell_width / 2) / CC_COLUMNS) * CC_COLUMNS + cell_width;
+ y = base_y - (renderer->max_char_height + 1) / 2;
+ }
+ else {
+ x = renderer->width * pos / CC_COLUMNS;
+ y = base_y;
+ }
+
+#ifdef LOG_DEBUG
+ printf("text_w, text_h = %d, %d\n", text_w, text_h);
+ printf("cc from %d to %d; text plotting from %d, %d (basey = %d)\n", pos, endpos, x, y, base_y);
+#endif
+
+ /* render text part by rendering each attributed text segment */
+ for (seg = 0; seg < num_seg; seg++) {
+ int textcol = text_colormap[this->cells[seg_attr[seg]].attributes.foreground];
+ int box_x1 = x + cumulative_seg_width[seg];
+ int box_x2 = x + cumulative_seg_width[seg + 1];
+
+#ifdef LOG_DEBUG
+ printf("ccrow_render: rendering segment %d from %d to %d / %d to %d\n",
+ seg, seg_pos[seg], seg_pos[seg + 1],
+ x + cumulative_seg_width[seg], x + cumulative_seg_width[seg + 1]);
+#endif
+ /* make caption background a uniform box. Without this line, the */
+ /* background is uneven for superscript characters. */
+ /* Also pad left & right ends of caption to make it more readable */
+/*FIXME: There may be off-by one errors in the rendering - check with Miguel*/
+ if (seg == 0)
+ box_x1 -= renderer->max_char_width;
+ if (seg == num_seg - 1)
+ box_x2 += renderer->max_char_width;
+ osd_renderer->filled_rect(renderer->cap_display, box_x1, y, box_x2,
+ y + renderer->max_char_height,
+ textcol + CAP_BG_COL);
+
+ for (i = seg_pos[seg]; i < seg_pos[seg + 1]; i++)
+ buf[i - seg_pos[seg]] = this->cells[i].c;
+ buf[seg_pos[seg + 1] - seg_pos[seg]] = '\0';
+ ccrow_set_attributes(renderer, this, seg_attr[seg]);
+
+ /* text is already mapped from EIA-608 into iso-8859-1 */
+ osd_renderer->render_text(renderer->cap_display,
+ x + cumulative_seg_width[seg], y, buf,
+ textcol);
+ }
+
+ pos = ccrow_find_next_text_part(this, endpos);
+ }
+}
+
+
+/*----------------- cc_buffer_t methods --------------------------------*/
+
+static int ccbuf_has_displayable(cc_buffer_t *this)
+{
+ int i;
+ for (i = 0; i < CC_ROWS; i++)
+ if (this->rows[i].num_chars > 0)
+ return 1;
+
+ return 0;
+}
+
+
+static void ccbuf_add_char(cc_buffer_t *this, uint8_t c)
+{
+ cc_row_t *rowbuf = &this->rows[this->rowpos];
+ int pos = rowbuf->pos;
+ int left_displayable = (pos > 0) && (pos <= rowbuf->num_chars);
+
+#if LOG_DEBUG > 2
+ printf("cc_decoder: ccbuf_add_char: %c @ %d/%d\n", c, this->rowpos, pos);
+#endif
+
+ if (pos >= CC_COLUMNS) {
+ printf("cc_decoder: ccbuf_add_char: row buffer overflow\n");
+ return;
+ }
+
+ if (pos > rowbuf->num_chars) {
+ /* fill up to indented position with transparent spaces, if necessary */
+ ccrow_fill_transp(rowbuf);
+ }
+
+ /* midrow PAC attributes are applied only if there is no displayable */
+ /* character to the immediate left. This makes the implementation rather */
+ /* complicated, but this is what the EIA-608 standard specifies. :-( */
+ if (rowbuf->pac_attr_chg && !rowbuf->attr_chg && !left_displayable) {
+ rowbuf->attr_chg = 1;
+ rowbuf->cells[pos].attributes = rowbuf->pac_attr;
+#ifdef LOG_DEBUG
+ printf("cc_decoder: ccbuf_add_char: Applying midrow PAC.\n");
+#endif
+ }
+
+ rowbuf->cells[pos].c = c;
+ rowbuf->cells[pos].midrow_attr = rowbuf->attr_chg;
+ rowbuf->pos++;
+
+ if (rowbuf->num_chars < rowbuf->pos)
+ rowbuf->num_chars = rowbuf->pos;
+
+ rowbuf->attr_chg = 0;
+ rowbuf->pac_attr_chg = 0;
+}
+
+
+static void ccbuf_set_cursor(cc_buffer_t *this, int row, int column,
+ int underline, int italics, int color)
+{
+ cc_row_t *rowbuf = &this->rows[row];
+ cc_attribute_t attr;
+
+ attr.italic = italics;
+ attr.underline = underline;
+ attr.foreground = color;
+ attr.background = BLACK;
+
+ rowbuf->pac_attr = attr;
+ rowbuf->pac_attr_chg = 1;
+
+ this->rowpos = row;
+ rowbuf->pos = column;
+ rowbuf->attr_chg = 0;
+}
+
+
+static void ccbuf_apply_attribute(cc_buffer_t *this, cc_attribute_t *attr)
+{
+ cc_row_t *rowbuf = &this->rows[this->rowpos];
+ int pos = rowbuf->pos;
+
+ rowbuf->attr_chg = 1;
+ rowbuf->cells[pos].attributes = *attr;
+ /* A midrow attribute always counts as a space */
+ ccbuf_add_char(this, chartbl[(unsigned int) ' ']);
+}
+
+
+static void ccbuf_tab(cc_buffer_t *this, int tabsize)
+{
+ cc_row_t *rowbuf = &this->rows[this->rowpos];
+ rowbuf->pos += tabsize;
+ if (rowbuf->pos > CC_COLUMNS) {
+#ifdef LOG_DEBUG
+ printf("cc_decoder: ccbuf_tab: row buffer overflow\n");
+#endif
+ rowbuf->pos = CC_COLUMNS;
+ return;
+ }
+ /* tabs have no effect on pending PAC attribute changes */
+}
+
+
+static void ccbuf_render(cc_renderer_t *renderer, cc_buffer_t *this)
+{
+ int row;
+
+#ifdef LOG_DEBUG
+ printf("cc_decoder: ccbuf_render\n");
+#endif
+
+ for (row = 0; row < CC_ROWS; ++row) {
+ if (this->rows[row].num_chars > 0)
+ ccrow_render(renderer, &this->rows[row], row);
+ }
+}
+
+
+/*----------------- cc_memory_t methods --------------------------------*/
+
+static void ccmem_clear(cc_memory_t *this)
+{
+#ifdef LOG_DEBUG
+ printf("cc_decoder.c: ccmem_clear: Clearing CC memory\n");
+#endif
+ memset(this, 0, sizeof (cc_memory_t));
+}
+
+
+static void ccmem_init(cc_memory_t *this)
+{
+ ccmem_clear(this);
+}
+
+
+static void ccmem_exit(cc_memory_t *this)
+{
+/*FIXME: anything to deallocate?*/
+}
+
+
+/*----------------- cc_renderer_t methods -------------------------------*/
+
+static void cc_renderer_build_palette(cc_renderer_t *this)
+{
+ int i, j;
+ const colorinfo_t *cc_text = cc_text_palettes[this->cc_state->cc_cfg->cc_scheme];
+ const uint8_t *cc_alpha = cc_alpha_palettes[this->cc_state->cc_cfg->cc_scheme];
+
+ memset(this->cc_palette, 0, sizeof (this->cc_palette));
+ memset(this->cc_trans, 0, sizeof (this->cc_trans));
+ for (i = 0; i < NUM_FG_COL; i++) {
+ /* background color */
+ this->cc_palette[i * TEXT_PALETTE_SIZE + 1 + OSD_TEXT1] =
+ *(uint32_t *) &cc_text[i].bgcol;
+ /* background -> border */
+ for (j = 2; j <= 5; j++) {
+ clut_t col = interpolate_color(cc_text[i].bgcol,
+ cc_text[i].bordercol, 4, j - 1);
+ this->cc_palette[i * TEXT_PALETTE_SIZE + j + OSD_TEXT1] =
+ *(uint32_t *) &col;
+ }
+ /* border color */
+ this->cc_palette[i * TEXT_PALETTE_SIZE + 6 + OSD_TEXT1] =
+ *(uint32_t *) &cc_text[i].bordercol;
+ /* border -> foreground */
+ for (j = 7; j <= 9; j++) {
+ clut_t col = interpolate_color(cc_text[i].bordercol,
+ cc_text[i].textcol, 3, j - 6);
+ this->cc_palette[i * TEXT_PALETTE_SIZE + j + OSD_TEXT1] =
+ *(uint32_t *) &col;
+ }
+ /* foreground color */
+ this->cc_palette[i * TEXT_PALETTE_SIZE + 10 + OSD_TEXT1] =
+ *(uint32_t *) &cc_text[i].textcol;
+
+ /* alpha values */
+ for (j = 0; j <= 10; j++)
+ this->cc_trans[i * TEXT_PALETTE_SIZE + j + OSD_TEXT1] = cc_alpha[j];
+ }
+}
+
+
+static int64_t cc_renderer_calc_vpts(cc_renderer_t *this, int64_t pts,
+ uint32_t ntsc_frame_offset)
+{
+ metronom_t *metronom = this->metronom;
+ int64_t vpts = metronom->got_spu_packet(metronom, pts);
+ return vpts + ntsc_frame_offset * NTSC_FRAME_DURATION;
+}
+
+
+/* returns true if a caption is on display */
+static int cc_renderer_on_display(cc_renderer_t *this)
+{
+ return this->displayed;
+}
+
+
+static void cc_renderer_hide_caption(cc_renderer_t *this, int64_t vpts)
+{
+ if ( ! this->displayed ) return;
+
+ this->osd_renderer->hide(this->cap_display, vpts);
+ this->displayed = 0;
+ this->last_hide_vpts = vpts;
+}
+
+
+static void cc_renderer_show_caption(cc_renderer_t *this, cc_buffer_t *buf,
+ int64_t vpts)
+{
+#ifdef LOG_DEBUG
+ printf("spucc: cc_renderer: show\n");
+#endif
+
+ if (this->displayed) {
+ cc_renderer_hide_caption(this, vpts);
+ printf("spucc: cc_renderer: show: OOPS - caption was already displayed!\n");
+ }
+
+ this->osd_renderer->clear(this->cap_display);
+ ccbuf_render(this, buf);
+ this->osd_renderer->set_position(this->cap_display,
+ this->x,
+ this->y);
+ vpts = MAX(vpts, this->last_hide_vpts);
+ this->osd_renderer->show(this->cap_display, vpts);
+
+ this->displayed = 1;
+ this->display_vpts = vpts;
+}
+
+
+static void cc_renderer_free_osd_object(cc_renderer_t *this)
+{
+ /* hide and free old displayed caption object if necessary */
+ if ( ! this->cap_display ) return;
+
+ cc_renderer_hide_caption(this, this->display_vpts);
+ this->osd_renderer->free_object(this->cap_display);
+ this->cap_display = NULL;
+}
+
+
+static void cc_renderer_adjust_osd_object(cc_renderer_t *this)
+{
+ cc_renderer_free_osd_object(this);
+
+#ifdef LOG_DEBUG
+ printf("spucc: cc_renderer: adjust_osd_object: creating %dx%d OSD object\n",
+ this->width, this->height);
+#endif
+
+ /* create display object */
+ this->cap_display = this->osd_renderer->new_object(this->osd_renderer,
+ this->width,
+ this->height);
+ this->osd_renderer->set_palette(this->cap_display, this->cc_palette,
+ this->cc_trans);
+ this->osd_renderer->set_encoding(this->cap_display, "iso-8859-1");
+}
+
+
+cc_renderer_t *cc_renderer_open(osd_renderer_t *osd_renderer,
+ metronom_t *metronom, cc_state_t *cc_state,
+ int video_width, int video_height)
+{
+ cc_renderer_t *this = calloc(1, sizeof (cc_renderer_t));
+
+ this->osd_renderer = osd_renderer;
+ this->metronom = metronom;
+ this->cc_state = cc_state;
+ cc_renderer_update_cfg(this, video_width, video_height);
+#ifdef LOG_DEBUG
+ printf("spucc: cc_renderer: open\n");
+#endif
+ return this;
+}
+
+
+void cc_renderer_close(cc_renderer_t *this_obj)
+{
+ cc_renderer_free_osd_object(this_obj);
+ free(this_obj);
+
+#ifdef LOG_DEBUG
+ printf("spucc: cc_renderer: close\n");
+#endif
+}
+
+
+void cc_renderer_update_cfg(cc_renderer_t *this_obj, int video_width,
+ int video_height)
+{
+ int fontw, fonth;
+ int required_w, required_h;
+
+ this_obj->video_width = video_width;
+ this_obj->video_height = video_height;
+
+ /* fill in text palette */
+ cc_renderer_build_palette(this_obj);
+
+ /* calculate preferred captioning area, as per the EIA-608 standard */
+ this_obj->x = this_obj->video_width * 10 / 100;
+ this_obj->y = this_obj->video_height * 10 / 100;
+ this_obj->width = this_obj->video_width * 80 / 100;
+ this_obj->height = this_obj->video_height * 80 / 100;
+
+ /* find maximum text width and height for normal & italic captioning */
+ /* font */
+ get_font_metrics(this_obj->osd_renderer, this_obj->cc_state->cc_cfg->font,
+ this_obj->cc_state->cc_cfg->font_size, &fontw, &fonth);
+ this_obj->max_char_width = fontw;
+ this_obj->max_char_height = fonth;
+ get_font_metrics(this_obj->osd_renderer, this_obj->cc_state->cc_cfg->italic_font,
+ this_obj->cc_state->cc_cfg->font_size, &fontw, &fonth);
+ this_obj->max_char_width = MAX(fontw, this_obj->max_char_width);
+ this_obj->max_char_height = MAX(fonth, this_obj->max_char_height);
+#ifdef LOG_DEBUG
+ printf("spucc: cc_renderer: update config: max text extents: %d, %d\n",
+ this_obj->max_char_width, this_obj->max_char_height);
+#endif
+
+ /* need to adjust captioning area to accommodate font? */
+ required_w = CC_COLUMNS * (this_obj->max_char_width + 1);
+ required_h = CC_ROWS * (this_obj->max_char_height + 1);
+ if (required_w > this_obj->width) {
+#ifdef LOG_DEBUG
+ printf("spucc: cc_renderer: update config: adjusting cap area width: %d\n",
+ required_w);
+#endif
+ this_obj->width = required_w;
+ this_obj->x = (this_obj->video_width - required_w) / 2;
+ }
+ if (required_h > this_obj->height) {
+#ifdef LOG_DEBUG
+ printf("spucc: cc_renderer: update config: adjusting cap area height: %d\n",
+ required_h);
+#endif
+ this_obj->height = required_h;
+ this_obj->y = (this_obj->video_height - required_h) / 2;
+ }
+
+ if (required_w <= this_obj->video_width &&
+ required_h <= this_obj->video_height) {
+ this_obj->cc_state->can_cc = 1;
+ cc_renderer_adjust_osd_object(this_obj);
+ }
+ else {
+ this_obj->cc_state->can_cc = 0;
+ cc_renderer_free_osd_object(this_obj);
+ printf("spucc: required captioning area %dx%d exceeds screen %dx%d!\n"
+ " Captions disabled. Perhaps you should choose a smaller"
+ " font?\n",
+ required_w, required_h, this_obj->video_width,
+ this_obj->video_height);
+ }
+}
+
+
+/*----------------- cc_decoder_t methods --------------------------------*/
+
+static void cc_set_channel(cc_decoder_t *this, int channel)
+{
+ (*this->active)->channel_no = channel;
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_set_channel: selecting channel %d\n", channel);
+#endif
+}
+
+
+static cc_buffer_t *active_ccbuffer(cc_decoder_t *this)
+{
+ cc_memory_t *mem = *this->active;
+ return &mem->channel[mem->channel_no];
+}
+
+
+static int cc_onscreen_displayable(cc_decoder_t *this)
+{
+ return ccbuf_has_displayable(&this->on_buf->channel[this->on_buf->channel_no]);
+}
+
+
+static void cc_hide_displayed(cc_decoder_t *this)
+{
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_hide_displayed\n");
+#endif
+
+ if (cc_renderer_on_display(this->cc_state->renderer)) {
+ int64_t vpts = cc_renderer_calc_vpts(this->cc_state->renderer, this->pts,
+ this->f_offset);
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_hide_displayed: hiding caption %u at vpts %u\n", this->capid, vpts);
+#endif
+ cc_renderer_hide_caption(this->cc_state->renderer, vpts);
+ }
+}
+
+
+static void cc_show_displayed(cc_decoder_t *this)
+{
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_show_displayed\n");
+#endif
+
+ if (cc_onscreen_displayable(this)) {
+ int64_t vpts = cc_renderer_calc_vpts(this->cc_state->renderer, this->pts,
+ this->f_offset);
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_show_displayed: showing caption %u at vpts %u\n", this->capid, vpts);
+#endif
+ this->capid++;
+ cc_renderer_show_caption(this->cc_state->renderer,
+ &this->on_buf->channel[this->on_buf->channel_no],
+ vpts);
+ }
+}
+
+
+static void cc_swap_buffers(cc_decoder_t *this)
+{
+ cc_memory_t *temp;
+
+ /* hide caption in displayed memory */
+ cc_hide_displayed(this);
+
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_swap_buffers: swapping caption memory\n");
+#endif
+ temp = this->on_buf;
+ this->on_buf = this->off_buf;
+ this->off_buf = temp;
+
+ /* show new displayed memory */
+ cc_show_displayed(this);
+}
+
+static void cc_decode_standard_char(cc_decoder_t *this, uint8_t c1, uint8_t c2)
+{
+ cc_buffer_t *buf = active_ccbuffer(this);
+ /* c1 always is a valid character */
+ ccbuf_add_char(buf, chartbl[c1]);
+ /* c2 might not be a printable character, even if c1 was */
+ if (c2 & 0x60)
+ ccbuf_add_char(buf, chartbl[c2]);
+}
+
+
+static void cc_decode_PAC(cc_decoder_t *this, int channel,
+ uint8_t c1, uint8_t c2)
+{
+ cc_buffer_t *buf;
+ int row, column = 0;
+ int underline, italics = 0, color;
+
+ /* There is one invalid PAC code combination. Ignore it. */
+ if (c1 == 0x10 && c2 > 0x5f)
+ return;
+
+ cc_set_channel(this, channel);
+ buf = active_ccbuffer(this);
+
+ row = rowdata[((c1 & 0x07) << 1) | ((c2 & 0x20) >> 5)];
+ if (c2 & 0x10) {
+ column = ((c2 & 0x0e) >> 1) * 4; /* preamble indentation */
+ color = WHITE; /* indented lines have white color */
+ }
+ else if ((c2 & 0x0e) == 0x0e) {
+ italics = 1; /* italics, they are always white */
+ color = WHITE;
+ }
+ else
+ color = (c2 & 0x0e) >> 1;
+ underline = c2 & 0x01;
+
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_decode_PAC: row %d, col %d, ul %d, it %d, clr %d\n",
+ row, column, underline, italics, color);
+#endif
+
+ ccbuf_set_cursor(buf, row, column, underline, italics, color);
+}
+
+
+static void cc_decode_ext_attribute(cc_decoder_t *this, int channel,
+ uint8_t c1, uint8_t c2)
+{
+ cc_set_channel(this, channel);
+}
+
+
+static void cc_decode_special_char(cc_decoder_t *this, int channel,
+ uint8_t c1, uint8_t c2)
+{
+ cc_buffer_t *buf;
+
+ cc_set_channel(this, channel);
+ buf = active_ccbuffer(this);
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_decode_special_char: Mapping %x to %x\n", c2, specialchar[c2 & 0xf]);
+#endif
+ ccbuf_add_char(buf, specialchar[c2 & 0xf]);
+}
+
+
+static void cc_decode_midrow_attr(cc_decoder_t *this, int channel,
+ uint8_t c1, uint8_t c2)
+{
+ cc_buffer_t *buf;
+ cc_attribute_t attr;
+
+ cc_set_channel(this, channel);
+ buf = active_ccbuffer(this);
+ if (c2 < 0x2e) {
+ attr.italic = 0;
+ attr.foreground = (c2 & 0xe) >> 1;
+ }
+ else {
+ attr.italic = 1;
+ attr.foreground = WHITE;
+ }
+ attr.underline = c2 & 0x1;
+ attr.background = BLACK;
+#ifdef LOG_DEBUG
+ printf("cc_decoder: cc_decode_midrow_attr: attribute %x\n", c2);
+ printf("cc_decoder: cc_decode_midrow_attr: ul %d, it %d, clr %d\n",
+ attr.underline, attr.italic, attr.foreground);
+#endif
+
+ ccbuf_apply_attribute(buf, &attr);
+}
+
+
+static void cc_decode_misc_control_code(cc_decoder_t *this, int channel,
+ uint8_t c1, uint8_t c2)
+{
+#ifdef LOG_DEBUG
+ printf("cc_decoder: decode_misc: decoding %x %x\n", c1, c2);
+#endif
+
+ cc_set_channel(this, channel);
+
+ switch (c2) { /* 0x20 <= c2 <= 0x2f */
+
+ case 0x20: /* RCL */
+ break;
+
+ case 0x21: /* backspace */
+#ifdef LOG_DEBUG
+ printf("cc_decoder: backspace\n");
+#endif
+ break;
+
+ case 0x24: /* DER */
+ break;
+
+ case 0x25: /* RU2 */
+ break;
+
+ case 0x26: /* RU3 */
+ break;
+
+ case 0x27: /* RU4 */
+ break;
+
+ case 0x28: /* FON */
+ break;
+
+ case 0x29: /* RDC */
+ break;
+
+ case 0x2a: /* TR */
+ break;
+
+ case 0x2b: /* RTD */
+ break;
+
+ case 0x2c: /* EDM - erase displayed memory */
+ cc_hide_displayed(this);
+ ccmem_clear(this->on_buf);
+ break;
+
+ case 0x2d: /* carriage return */
+ break;
+
+ case 0x2e: /* ENM - erase non-displayed memory */
+ ccmem_clear(this->off_buf);
+ break;
+
+ case 0x2f: /* EOC - swap displayed and non displayed memory */
+ cc_swap_buffers(this);
+ break;
+ }
+}
+
+
+static void cc_decode_tab(cc_decoder_t *this, int channel,
+ uint8_t c1, uint8_t c2)
+{
+ cc_buffer_t *buf;
+
+ cc_set_channel(this, channel);
+ buf = active_ccbuffer(this);
+ ccbuf_tab(buf, c2 & 0x3);
+}
+
+
+static void cc_decode_EIA608(cc_decoder_t *this, uint16_t data)
+{
+ uint8_t c1 = data & 0x7f;
+ uint8_t c2 = (data >> 8) & 0x7f;
+
+#if LOG_DEBUG >= 3
+ printf("decoding %x %x\n", c1, c2);
+#endif
+
+ if (c1 & 0x60) { /* normal character, 0x20 <= c1 <= 0x7f */
+ cc_decode_standard_char(this, c1, c2);
+ }
+ else if (c1 & 0x10) { /* control code or special character */
+ /* 0x10 <= c1 <= 0x1f */
+ int channel = (c1 & 0x08) >> 3;
+ c1 &= ~0x08;
+
+ /* control sequences are often repeated. In this case, we should */
+ /* evaluate it only once. */
+ if (data != this->lastcode) {
+
+ if (c2 & 0x40) { /* preamble address code: 0x40 <= c2 <= 0x7f */
+ cc_decode_PAC(this, channel, c1, c2);
+ }
+ else {
+ switch (c1) {
+
+ case 0x10: /* extended background attribute code */
+ cc_decode_ext_attribute(this, channel, c1, c2);
+ break;
+
+ case 0x11: /* attribute or special character */
+ if ((c2 & 0x30) == 0x30) { /* special char: 0x30 <= c2 <= 0x3f */
+ cc_decode_special_char(this, channel, c1, c2);
+ }
+ else if (c2 & 0x20) { /* midrow attribute: 0x20 <= c2 <= 0x2f */
+ cc_decode_midrow_attr(this, channel, c1, c2);
+ }
+ break;
+
+ case 0x14: /* possibly miscellaneous control code */
+ cc_decode_misc_control_code(this, channel, c1, c2);
+ break;
+
+ case 0x17: /* possibly misc. control code TAB offset */
+ /* 0x21 <= c2 <= 0x23 */
+ if (c2 >= 0x21 && c2 <= 0x23) {
+ cc_decode_tab(this, channel, c1, c2);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ this->lastcode = data;
+}
+
+
+void decode_cc(cc_decoder_t *this, uint8_t *buffer, uint32_t buf_len,
+ int64_t pts)
+{
+ /* The first number may denote a channel number. I don't have the
+ * EIA-708 standard, so it is hard to say.
+ * From what I could figure out so far, the general format seems to be:
+ *
+ * repeat
+ *
+ * 0xfe starts 2 byte sequence of unknown purpose. It might denote
+ * field #2 in line 21 of the VBI. We'll ignore it for the
+ * time being.
+ *
+ * 0xff starts 2 byte EIA-608 sequence, field #1 in line 21 of the VBI.
+ * Followed by a 3-code triplet that starts either with 0xff or
+ * 0xfe. In either case, the following triplet needs to be ignored
+ * for line 21, field 1.
+ *
+ * 0x00 is padding, followed by 2 more 0x00.
+ *
+ * 0x01 always seems to appear at the beginning, always seems to
+ * be followed by 0xf8, 8-bit number.
+ * The lower 7 bits of this 8-bit number seem to denote the
+ * number of code triplets that follow.
+ * The most significant bit denotes whether the Line 21 field 1
+ * captioning information is at odd or even triplet offsets from this
+ * beginning triplet. 1 denotes odd offsets, 0 denotes even offsets.
+ *
+ * Most captions are encoded with odd offsets, so this is what we
+ * will assume.
+ *
+ * until end of packet
+ */
+ uint8_t *current = buffer;
+ uint32_t curbytes = 0;
+ uint8_t data1, data2;
+ uint8_t cc_code;
+ int odd_offset = 1;
+
+ this->f_offset = 0;
+ this->pts = pts;
+
+#if LOG_DEBUG >= 2
+ printf("libspucc: decode_cc: got pts %u\n", pts);
+ {
+ uint8_t *cur_d = buffer;
+ printf("libspucc: decode_cc: codes: ");
+ while (cur_d < buffer + buf_len) {
+ printf("0x%0x ", *cur_d++);
+ }
+ printf("\n");
+ }
+#endif
+
+ while (curbytes < buf_len) {
+ int skip = 2;
+
+ cc_code = *current++;
+ curbytes++;
+
+ if (buf_len - curbytes < 2) {
+#ifdef LOG_DEBUG
+ fprintf(stderr, "Not enough data for 2-byte CC encoding\n");
+#endif
+ break;
+ }
+
+ data1 = *current;
+ data2 = *(current + 1);
+
+ switch (cc_code) {
+ case 0xfe:
+ /* expect 2 byte encoding (perhaps CC3, CC4?) */
+ /* ignore for time being */
+ skip = 2;
+ break;
+
+ case 0xff:
+ /* expect EIA-608 CC1/CC2 encoding */
+ if (good_parity(data1 | (data2 << 8))) {
+ cc_decode_EIA608(this, data1 | (data2 << 8));
+ this->f_offset++;
+ }
+ skip = 5;
+ break;
+
+ case 0x00:
+ /* This seems to be just padding */
+ skip = 2;
+ break;
+
+ case 0x01:
+ odd_offset = data2 & 0x80;
+ if (odd_offset)
+ skip = 2;
+ else
+ skip = 5;
+ break;
+
+ default:
+#ifdef LOG_DEBUG
+ fprintf(stderr, "Unknown CC encoding: %x\n", cc_code);
+#endif
+ skip = 2;
+ break;
+ }
+ current += skip;
+ curbytes += skip;
+ }
+}
+
+
+
+cc_decoder_t *cc_decoder_open(cc_state_t *cc_state)
+{
+ cc_decoder_t *this = calloc(1, sizeof (cc_decoder_t));
+ /* configfile stuff */
+ this->cc_state = cc_state;
+
+ ccmem_init(&this->buffer[0]);
+ ccmem_init(&this->buffer[1]);
+ this->on_buf = &this->buffer[0];
+ this->off_buf = &this->buffer[1];
+ this->active = &this->off_buf;
+
+ this->lastcode = 0;
+ this->capid = 0;
+
+ this->pts = this->f_offset = 0;
+
+#ifdef LOG_DEBUG
+ printf("spucc: cc_decoder_open\n");
+#endif
+ return this;
+}
+
+
+void cc_decoder_close(cc_decoder_t *this)
+{
+ ccmem_exit(&this->buffer[0]);
+ ccmem_exit(&this->buffer[1]);
+
+ free(this);
+
+#ifdef LOG_DEBUG
+ printf("spucc: cc_decoder_close\n");
+#endif
+}
diff --git a/src/spu_dec/cc_decoder.h b/src/spu_dec/cc_decoder.h
new file mode 100644
index 000000000..58bd5aa9a
--- /dev/null
+++ b/src/spu_dec/cc_decoder.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2000-2008 the xine project
+ *
+ * Copyright (C) Christian Vogler
+ * cvogler@gradient.cis.upenn.edu - December 2001
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * stuff needed to provide closed captioning decoding and display
+ *
+ * Some small bits and pieces of the EIA-608 captioning decoder were
+ * adapted from CCDecoder 0.9.1 by Mike Baker. The latest version is
+ * available at http://sourceforge.net/projects/ccdecoder/.
+ */
+
+typedef struct cc_decoder_s cc_decoder_t;
+typedef struct cc_renderer_s cc_renderer_t;
+
+#define NUM_CC_PALETTES 2
+#define CC_FONT_MAX 256
+
+typedef struct cc_config_s {
+ int cc_enabled; /* true if closed captions are enabled */
+ char font[CC_FONT_MAX]; /* standard captioning font & size */
+ int font_size;
+ char italic_font[CC_FONT_MAX]; /* italic captioning font & size */
+ int center; /* true if captions should be centered */
+ /* according to text width */
+ int cc_scheme; /* which captioning scheme to use */
+
+ int config_version; /* the decoder should be updated when this is increased */
+} cc_config_t;
+
+typedef struct spucc_class_s {
+ spu_decoder_class_t spu_class;
+ cc_config_t cc_cfg;
+} spucc_class_t;
+
+typedef struct cc_state_s {
+ cc_config_t *cc_cfg;
+ /* the following variables are not controlled by configuration files; they */
+ /* are intrinsic to the properties of the configuration options and the */
+ /* currently played video */
+ int can_cc; /* true if captions can be displayed */
+ /* (e.g., font fits on screen) */
+ cc_renderer_t *renderer; /* closed captioning renderer */
+} cc_state_t;
+
+cc_decoder_t *cc_decoder_open(cc_state_t *cc_state);
+void cc_decoder_close(cc_decoder_t *this_obj);
+
+void decode_cc(cc_decoder_t *this, uint8_t *buffer, uint32_t buf_len,
+ int64_t pts);
+
+/* Instantiates a new closed captioning renderer. */
+cc_renderer_t *cc_renderer_open(osd_renderer_t *osd_renderer,
+ metronom_t *metronom, cc_state_t *cc_state,
+ int video_width, int video_height);
+
+/* Destroys a closed captioning renderer. */
+void cc_renderer_close(cc_renderer_t *this_obj);
+
+/* Updates the renderer configuration variables */
+void cc_renderer_update_cfg(cc_renderer_t *this_obj, int video_width,
+ int video_height);
+
diff --git a/src/spu_dec/cmml_decoder.c b/src/spu_dec/cmml_decoder.c
new file mode 100644
index 000000000..53d5fa9ea
--- /dev/null
+++ b/src/spu_dec/cmml_decoder.c
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2000-2008 the xine project
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ */
+
+#define LOG_MODULE "libspucmml"
+#define LOG_VERBOSE
+/*
+#define LOG
+*/
+#define LOG_OSD 0
+#define LOG_SCHEDULING 0
+#define LOG_WIDTH 0
+
+#define SUB_BUFSIZE 1024
+#define SUB_MAX_TEXT 5
+
+#include <xine/xine_internal.h>
+
+typedef enum {
+ SUBTITLE_SIZE_SMALL = 0,
+ SUBTITLE_SIZE_NORMAL,
+ SUBTITLE_SIZE_LARGE,
+
+ SUBTITLE_SIZE_NUM /* number of values in enum */
+} subtitle_size;
+
+
+typedef struct spucmml_class_s {
+ spu_decoder_class_t class;
+ char *src_encoding; /* encoding of subtitle file */
+ xine_t *xine;
+
+} spucmml_class_t;
+
+
+typedef struct cmml_anchor_s {
+ char *text;
+ char *href;
+} cmml_anchor_t;
+
+
+typedef struct spucmml_decoder_s {
+ spu_decoder_t spu_decoder;
+
+ spucmml_class_t *class;
+ xine_stream_t *stream;
+
+ xine_event_queue_t *event_queue;
+
+ int lines;
+ char text[SUB_MAX_TEXT][SUB_BUFSIZE];
+
+ int cached_width; /* frame width */
+ int cached_height; /* frame height */
+ int64_t cached_img_duration;
+ int font_size;
+ int line_height;
+ int master_started;
+ int slave_started;
+
+ char *font; /* subtitle font */
+ subtitle_size subtitle_size; /* size of subtitles */
+ int vertical_offset;
+
+ osd_object_t *osd;
+
+ cmml_anchor_t current_anchor;
+} spucmml_decoder_t;
+
+
+static void video_frame_format_change_callback (void *user_data, const xine_event_t *event);
+
+
+static void update_font_size (spucmml_decoder_t *this) {
+ static const int sizes[SUBTITLE_SIZE_NUM][4] = {
+ { 16, 16, 16, 20 }, /* SUBTITLE_SIZE_SMALL */
+ { 16, 16, 20, 24 }, /* SUBTITLE_SIZE_NORMAL */
+ { 16, 20, 24, 32 }, /* SUBTITLE_SIZE_LARGE */
+ };
+
+ const int *const vec = sizes[this->subtitle_size];
+
+ if( this->cached_width >= 512 )
+ this->font_size = vec[3];
+ else if( this->cached_width >= 384 )
+ this->font_size = vec[2];
+ else if( this->cached_width >= 320 )
+ this->font_size = vec[1];
+ else
+ this->font_size = vec[0];
+
+ this->line_height = this->font_size + 10;
+
+ int y = this->cached_height - (SUB_MAX_TEXT * this->line_height) - 5;
+
+ if(((y - this->vertical_offset) >= 0) && ((y - this->vertical_offset) <= this->cached_height))
+ y -= this->vertical_offset;
+
+ /* TODO: we should move this stuff below into another function */
+
+ if (this->osd)
+ this->stream->osd_renderer->free_object (this->osd);
+
+ llprintf (LOG_OSD,
+ "pre new_object: osd=%p, osd_renderer=%p, width=%d, height=%d\n",
+ this->osd,
+ this->stream->osd_renderer,
+ this->cached_width,
+ SUB_MAX_TEXT * this->line_height);
+
+ this->osd = this->stream->osd_renderer->new_object (this->stream->osd_renderer,
+ this->cached_width, SUB_MAX_TEXT * this->line_height);
+
+ llprintf (LOG_OSD, "post new_object: osd is %p\n", this->osd);
+
+ if(this->stream->osd_renderer) {
+ this->stream->osd_renderer->set_font (this->osd, this->font, this->font_size);
+ this->stream->osd_renderer->set_position (this->osd, 0, y);
+ }
+}
+
+static int get_width(spucmml_decoder_t *this, char* text) {
+ int width=0;
+
+ while (1)
+ switch (*text) {
+ case '\0':
+ llprintf(LOG_WIDTH, "get_width returning width of %d\n", width);
+ return width;
+
+ case '<':
+ if (!strncmp("<b>", text, 3)) {
+ /*Do somethink to enable BOLD typeface*/
+ text += 3;
+ break;
+ } else if (!strncmp("</b>", text, 3)) {
+ /*Do somethink to disable BOLD typeface*/
+ text += 4;
+ break;
+ } else if (!strncmp("<i>", text, 3)) {
+ /*Do somethink to enable italics typeface*/
+ text += 3;
+ break;
+ } else if (!strncmp("</i>", text, 3)) {
+ /*Do somethink to disable italics typeface*/
+ text += 4;
+ break;
+ } else if (!strncmp("<font>", text, 3)) {
+ /*Do somethink to disable typing
+ fixme - no teststreams*/
+ text += 6;
+ break;
+ } else if (!strncmp("</font>", text, 3)) {
+ /*Do somethink to enable typing
+ fixme - no teststreams*/
+ text += 7;
+ break;
+ }
+ default:
+ {
+ int w, dummy;
+ const char letter[2] = { *text, '\0' };
+ this->stream->osd_renderer->get_text_size(this->osd, letter, &w, &dummy);
+ width += w;
+ text++;
+ }
+ }
+}
+
+static void render_line(spucmml_decoder_t *this, int x, int y, char* text) {
+ while (*text != '\0') {
+ int w, dummy;
+ const char letter[2] = { *text, '\0' };
+
+ this->stream->osd_renderer->render_text(this->osd, x, y, letter, OSD_TEXT1);
+ this->stream->osd_renderer->get_text_size(this->osd, letter, &w, &dummy);
+ x += w;
+ text++;
+ }
+}
+
+static void draw_subtitle(spucmml_decoder_t *this, int64_t sub_start) {
+
+ this->stream->osd_renderer->filled_rect (this->osd, 0, 0,
+ this->cached_width-1, this->line_height * SUB_MAX_TEXT - 1, 0);
+
+ const int y = (SUB_MAX_TEXT - this->lines) * this->line_height;
+ int font_size = this->font_size;
+ this->stream->osd_renderer->set_encoding(this->osd, this->class->src_encoding);
+
+ int line;
+
+ for (line=0; line<this->lines; line++) {
+ int x;
+ while(1) {
+ const int w = get_width( this, this->text[line]);
+ x = (this->cached_width - w) / 2;
+
+ if( w > this->cached_width && font_size > 16 ) {
+ font_size -= 4;
+ this->stream->osd_renderer->set_font (this->osd, this->font, font_size);
+ } else {
+ break;
+ }
+ }
+ render_line(this, x, y + line*this->line_height, this->text[line]);
+ }
+
+ if( font_size != this->font_size )
+ this->stream->osd_renderer->set_font (this->osd, this->font, this->font_size);
+
+
+ this->stream->osd_renderer->set_text_palette (this->osd, -1, OSD_TEXT1);
+ this->stream->osd_renderer->show (this->osd, sub_start);
+
+ llprintf (LOG_SCHEDULING,
+ "spucmml: scheduling subtitle >%s< at %"PRId64", current time is %"PRId64"\n",
+ this->text[0], sub_start,
+ this->stream->xine->clock->get_current_time (this->stream->xine->clock));
+}
+
+static void spudec_decode_data (spu_decoder_t *this_gen, buf_element_t *buf) {
+
+ spucmml_decoder_t *this = (spucmml_decoder_t *) this_gen;
+
+ xml_parser_t *xml_parser;
+ xml_node_t *packet_xml_root;
+ char * anchor_text = NULL;
+
+ lprintf("CMML packet seen\n");
+
+ char *str = (char *) buf->content;
+
+ /* parse the CMML */
+
+ xml_parser = xml_parser_init_r (str, strlen (str), XML_PARSER_CASE_INSENSITIVE);
+ if (xml_parser_build_tree_r(xml_parser, &packet_xml_root) != XML_PARSER_OK) {
+ lprintf ("warning: invalid XML packet detected in CMML track\n");
+ xml_parser_finalize_r(xml_parser);
+ return;
+ }
+
+ xml_parser_finalize_r(xml_parser);
+
+ if (strcasecmp(packet_xml_root->name, "head") == 0) {
+ /* found a <head>...</head> packet: need to parse the title */
+
+ xml_node_t *title_node;
+
+ /* iterate through children trying to find the title node */
+
+ for (title_node = packet_xml_root->child; title_node != NULL; title_node = title_node->next) {
+
+ if (title_node->data &&
+ strcasecmp (title_node->name, "title") == 0) {
+ /* found a title node */
+
+ xine_ui_data_t data = {
+ .str_len = strlen(title_node->data) + 1
+ };
+ xine_event_t uevent = {
+ .type = XINE_EVENT_UI_SET_TITLE,
+ .stream = this->stream,
+ .data = &data,
+ .data_length = sizeof(data),
+ };
+ strncpy(data.str, title_node->data, sizeof(data.str)-1);
+
+ /* found a non-empty title */
+ lprintf ("found title: \"%s\"\n", data.str);
+
+ /* set xine meta-info */
+ _x_meta_info_set(this->stream, XINE_META_INFO_TITLE, strdup(data.str));
+
+ /* and push out a new event signifying the title update on the event
+ * queue */
+ xine_event_send(this->stream, &uevent);
+ }
+ }
+ } else if (strcasecmp(packet_xml_root->name, "clip") == 0) {
+ /* found a <clip>...</clip> packet: search for the <a href="..."> in it */
+ xml_node_t *clip_node;
+
+ /* iterate through each tag contained in the <clip> tag to look for <a> */
+
+ for (clip_node = packet_xml_root->child; clip_node != NULL; clip_node = clip_node->next) {
+
+ if (strcasecmp (clip_node->name, "a") == 0) {
+ xml_property_t *href_property;
+
+ /* found the <a> tag: grab its value and its href property */
+
+ if (clip_node->data)
+ anchor_text = strdup (clip_node->data);
+
+ for (href_property = clip_node->props; href_property != NULL; href_property = href_property->next) {
+ if (strcasecmp (href_property->name, "href") == 0) {
+ /* found the href property */
+ char *href = href_property->value;
+
+ if (href) {
+ lprintf ("found href: \"%s\"\n", href);
+ this->current_anchor.href = strdup(href);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* finish here if we don't have to process any anchor text */
+ if (!anchor_text)
+ return;
+
+ /* how many lines does the anchor text take up? */
+ this->lines=0;
+ {
+ int i = 0, index = 0;
+ while (anchor_text[index]) {
+ if (anchor_text[index] == '\r' || anchor_text[index] == '\n') {
+ if (i) {
+ /* match a newline and there are chars on the current line ... */
+ this->text[ this->lines ][i] = '\0';
+ this->lines++;
+ i = 0;
+ }
+ } else {
+ /* found a normal (non-line-ending) character */
+ this->text[ this->lines ][i] = anchor_text[index];
+ if (i<SUB_BUFSIZE-1)
+ i++;
+ }
+ index++;
+ }
+
+ /* always NULL-terminate the string */
+ if (i) {
+ this->text[ this->lines ][i] = '\0';
+ this->lines++;
+ }
+ }
+ free (anchor_text);
+
+ /* initialize decoder if needed */
+ if( !this->cached_width || !this->cached_height || !this->cached_img_duration || !this->osd ) {
+ if( this->stream->video_out->status(this->stream->video_out, NULL,
+ &this->cached_width, &this->cached_height, &this->cached_img_duration )) {
+ if( this->cached_width && this->cached_height && this->cached_img_duration ) {
+ lprintf("this->stream->osd_renderer is %p\n", this->stream->osd_renderer);
+ }
+ }
+ }
+
+ update_font_size (this);
+
+ if( this->osd ) {
+ draw_subtitle(this, buf->pts);
+ return;
+ } else {
+ lprintf ("libspucmml: no osd\n");
+ }
+
+ return;
+}
+
+static void video_frame_format_change_callback (void *user_data, const xine_event_t *event)
+{
+ /* this doesn't do anything for now: it's a start at attempting to display
+ * CMML clips which occur at 0 seconds into the track. see
+ *
+ * http://marc.theaimsgroup.com/?l=xine-devel&m=109202443013890&w=2
+ *
+ * for a description of the problem. */
+
+ switch (event->type) {
+ case XINE_EVENT_FRAME_FORMAT_CHANGE:
+ lprintf("video_frame_format_change_callback called!\n");
+ break;
+ default:
+ lprintf("video_frame_format_change_callback called with unknown event %d\n", event->type);
+ break;
+ }
+}
+
+static void spudec_reset (spu_decoder_t *this_gen) {
+ spucmml_decoder_t *this = (spucmml_decoder_t *) this_gen;
+
+ this->cached_width = this->cached_height = 0;
+}
+
+static void spudec_discontinuity (spu_decoder_t *this_gen) {
+ /* do nothing */
+}
+
+static void spudec_dispose (spu_decoder_t *this_gen) {
+ spucmml_decoder_t *this = (spucmml_decoder_t *) this_gen;
+
+ if (this->event_queue)
+ xine_event_dispose_queue (this->event_queue);
+
+ if (this->osd) {
+ this->stream->osd_renderer->free_object (this->osd);
+ this->osd = NULL;
+ }
+ free(this);
+}
+
+static void update_vertical_offset(void *this_gen, xine_cfg_entry_t *entry)
+{
+ spucmml_decoder_t *this = (spucmml_decoder_t *)this_gen;
+
+ this->vertical_offset = entry->num_value;
+ update_font_size(this);
+}
+
+static void update_osd_font(void *this_gen, xine_cfg_entry_t *entry)
+{
+ spucmml_decoder_t *this = (spucmml_decoder_t *)this_gen;
+
+ this->font = entry->str_value;
+
+ if( this->stream->osd_renderer )
+ this->stream->osd_renderer->set_font (this->osd, this->font, this->font_size);
+}
+
+static spu_decoder_t *spucmml_class_open_plugin (spu_decoder_class_t *class_gen, xine_stream_t *stream) {
+ spucmml_class_t *class = (spucmml_class_t *)class_gen;
+ spucmml_decoder_t *this = (spucmml_decoder_t *) calloc(1, sizeof(spucmml_decoder_t));
+
+ this->spu_decoder.decode_data = spudec_decode_data;
+ this->spu_decoder.reset = spudec_reset;
+ this->spu_decoder.discontinuity = spudec_discontinuity;
+ this->spu_decoder.dispose = spudec_dispose;
+ this->spu_decoder.get_interact_info = NULL;
+ this->spu_decoder.set_button = NULL;
+ this->spu_decoder.dispose = spudec_dispose;
+
+ this->class = class;
+ this->stream = stream;
+
+ this->event_queue = xine_event_new_queue (this->stream);
+ xine_event_create_listener_thread (this->event_queue,
+ video_frame_format_change_callback,
+ this);
+
+ this->font_size = 24;
+ this->subtitle_size = 1;
+
+ this->font = class->xine->config->register_string(class->xine->config,
+ "subtitles.separate.font",
+ "sans",
+ _("font for external subtitles"),
+ NULL, 0, update_osd_font, this);
+
+ this->vertical_offset = class->xine->config->register_num(class->xine->config,
+ "subtitles.separate.vertical_offset",
+ 0,
+ _("subtitle vertical offset (relative window size)"),
+ NULL, 0, update_vertical_offset, this);
+
+ this->current_anchor.href = NULL;
+
+ lprintf ("video_out is at %p\n", this->stream->video_out);
+
+ return (spu_decoder_t *) this;
+}
+
+static void update_src_encoding(void *this_gen, xine_cfg_entry_t *entry)
+{
+ spucmml_class_t *this = (spucmml_class_t *)this_gen;
+
+ this->src_encoding = entry->str_value;
+ printf("libspucmml: spu_src_encoding = %s\n", this->src_encoding );
+}
+
+static void *init_spu_decoder_plugin (xine_t *xine, void *data) {
+ spucmml_class_t *this = (spucmml_class_t *) calloc(1, sizeof(spucmml_class_t));
+
+ this->class.open_plugin = spucmml_class_open_plugin;
+ this->class.identifier = "spucmml";
+ this->class.description = N_("CMML subtitle decoder plugin");
+ this->class.dispose = default_spu_decoder_class_dispose;
+
+ this->xine = xine;
+
+ this->src_encoding = xine->config->register_string(xine->config,
+ "subtitles.separate.src_encoding",
+ "iso-8859-1",
+ _("encoding of subtitles"),
+ NULL, 10, update_src_encoding, this);
+
+ return &this->class;
+}
+
+
+/* plugin catalog information */
+static const uint32_t supported_types[] = { BUF_SPU_CMML, 0 };
+
+static const decoder_info_t spudec_info = {
+ supported_types, /* supported types */
+ 1 /* priority */
+};
+
+const plugin_info_t xine_plugin_info[] EXPORTED = {
+ /* type, API, "name", version, special_info, init_function */
+ { PLUGIN_SPU_DECODER, 17, "spucmml", XINE_VERSION_CODE, &spudec_info, &init_spu_decoder_plugin },
+ { PLUGIN_NONE, 0, "", 0, NULL, NULL }
+};
+
diff --git a/src/spu_dec/nav_read.c b/src/spu_dec/nav_read.c
new file mode 100644
index 000000000..5244bfdd6
--- /dev/null
+++ b/src/spu_dec/nav_read.c
@@ -0,0 +1 @@
+#include "../input/libdvdnav/nav_read.c"
diff --git a/src/spu_dec/spu_decoder.c b/src/spu_dec/spu_decoder.c
new file mode 100644
index 000000000..9244564ff
--- /dev/null
+++ b/src/spu_dec/spu_decoder.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2000-2008 the xine project
+ *
+ * Copyright (C) James Courtier-Dutton James@superbug.demon.co.uk - July 2001
+ *
+ * This file is part of xine, a unix video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * stuff needed to turn libspu into a xine decoder plugin
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <xine/xine_internal.h>
+#include <xine/buffer.h>
+#include "xine-engine/bswap.h"
+#include <xine/xineutils.h>
+#ifdef HAVE_DVDNAV
+# ifdef HAVE_DVDNAV_NAVTYPES_H
+# include <dvdnav/nav_types.h>
+# include <dvdnav/nav_read.h>
+# else
+# include <dvdread/nav_types.h>
+# include <dvdread/nav_read.h>
+# endif
+#else
+# include "nav_read.h"
+# include "nav_types.h"
+#endif
+
+#include "spudec.h"
+
+/*
+#define LOG_DEBUG 1
+#define LOG_BUTTON 1
+*/
+
+static const clut_t default_clut[] = {
+ CLUT_Y_CR_CB_INIT(0x00, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xbf, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x10, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x28, 0x6d, 0xef),
+ CLUT_Y_CR_CB_INIT(0x51, 0xef, 0x5a),
+ CLUT_Y_CR_CB_INIT(0xbf, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x36, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x28, 0x6d, 0xef),
+ CLUT_Y_CR_CB_INIT(0xbf, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x51, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xbf, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x10, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x28, 0x6d, 0xef),
+ CLUT_Y_CR_CB_INIT(0x5c, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0xbf, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x1c, 0x80, 0x80),
+ CLUT_Y_CR_CB_INIT(0x28, 0x6d, 0xef)
+};
+
+static void spudec_decode_data (spu_decoder_t *this_gen, buf_element_t *buf) {
+ spudec_decoder_t *this = (spudec_decoder_t *) this_gen;
+ const uint8_t stream_id = buf->type & 0x1f ;
+
+#ifdef LOG_DEBUG
+ printf("libspudec:got buffer type = %x\n", buf->type);
+#endif
+
+ /* check, if we need to process the next PCI from the list */
+ pthread_mutex_lock(&this->nav_pci_lock);
+ spudec_update_nav(this);
+ pthread_mutex_unlock(&this->nav_pci_lock);
+
+ if ( (buf->type & 0xffff0000) != BUF_SPU_DVD ||
+ !(buf->decoder_flags & BUF_FLAG_SPECIAL) ||
+ buf->decoder_info[1] != BUF_SPECIAL_SPU_DVD_SUBTYPE )
+ return;
+
+ if ( buf->decoder_info[2] == SPU_DVD_SUBTYPE_CLUT ) {
+#ifdef LOG_DEBUG
+ printf("libspudec: SPU CLUT\n");
+#endif
+ if (buf->content[0]) { /* cheap endianess detection */
+ xine_fast_memcpy(this->state.clut, buf->content, sizeof(uint32_t)*16);
+ } else {
+ int i;
+ uint32_t *clut = (uint32_t*) buf->content;
+ for (i = 0; i < 16; i++)
+ this->state.clut[i] = bswap_32(clut[i]);
+ }
+ this->state.need_clut = 0;
+ return;
+ }
+
+ if ( buf->decoder_info[2] == SPU_DVD_SUBTYPE_NAV ) {
+#ifdef LOG_DEBUG
+ printf("libspudec:got nav packet 1\n");
+#endif
+ spudec_decode_nav(this,buf);
+ return;
+ }
+
+ if ( buf->decoder_info[2] == SPU_DVD_SUBTYPE_VOBSUB_PACKAGE ) {
+ this->state.vobsub = 1;
+ }
+
+#ifdef LOG_DEBUG
+ printf("libspudec:got buffer type = %x\n", buf->type);
+#endif
+ if (buf->decoder_flags & BUF_FLAG_PREVIEW) /* skip preview data */
+ return;
+
+ if (buf->pts) {
+ metronom_t *metronom = this->stream->metronom;
+ int64_t vpts = metronom->got_spu_packet(metronom, buf->pts);
+
+ this->spudec_stream_state[stream_id].vpts = vpts; /* Show timer */
+ this->spudec_stream_state[stream_id].pts = buf->pts; /* Required to match up with NAV packets */
+ }
+
+ spudec_reassembly(this->stream->xine,
+ &this->spudec_stream_state[stream_id].ra_seq, buf->content, buf->size);
+ if(this->spudec_stream_state[stream_id].ra_seq.complete == 1) {
+ if(this->spudec_stream_state[stream_id].ra_seq.broken) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, "libspudec: dropping broken SPU\n");
+ this->spudec_stream_state[stream_id].ra_seq.broken = 0;
+ } else
+ spudec_process(this,stream_id);
+ }
+}
+
+static void spudec_reset (spu_decoder_t *this_gen) {
+ spudec_decoder_t *this = (spudec_decoder_t *) this_gen;
+ video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out);
+ int i;
+
+ if( this->menu_handle >= 0 )
+ ovl_manager->free_handle(ovl_manager,
+ this->menu_handle);
+ this->menu_handle = -1;
+
+ for (i=0; i < MAX_STREAMS; i++) {
+ if( this->spudec_stream_state[i].overlay_handle >= 0 )
+ ovl_manager->free_handle(ovl_manager,
+ this->spudec_stream_state[i].overlay_handle);
+ this->spudec_stream_state[i].overlay_handle = -1;
+ this->spudec_stream_state[i].ra_seq.complete = 1;
+ this->spudec_stream_state[i].ra_seq.broken = 0;
+ }
+
+ pthread_mutex_lock(&this->nav_pci_lock);
+ spudec_clear_nav_list(this);
+ pthread_mutex_unlock(&this->nav_pci_lock);
+}
+
+static void spudec_discontinuity (spu_decoder_t *this_gen) {
+ spudec_decoder_t *this = (spudec_decoder_t *) this_gen;
+
+ pthread_mutex_lock(&this->nav_pci_lock);
+ spudec_clear_nav_list(this);
+ pthread_mutex_unlock(&this->nav_pci_lock);
+}
+
+
+static void spudec_dispose (spu_decoder_t *this_gen) {
+
+ spudec_decoder_t *this = (spudec_decoder_t *) this_gen;
+ video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out);
+
+ if( this->menu_handle >= 0 )
+ ovl_manager->free_handle(ovl_manager,
+ this->menu_handle);
+ this->menu_handle = -1;
+
+ int i;
+ for (i=0; i < MAX_STREAMS; i++) {
+ if( this->spudec_stream_state[i].overlay_handle >= 0 )
+ ovl_manager->free_handle(ovl_manager,
+ this->spudec_stream_state[i].overlay_handle);
+ this->spudec_stream_state[i].overlay_handle = -1;
+ free (this->spudec_stream_state[i].ra_seq.buf);
+ }
+
+ spudec_clear_nav_list(this);
+ pthread_mutex_destroy(&this->nav_pci_lock);
+
+ free (this->event.object.overlay);
+ free (this);
+}
+
+/* gets the current already correctly processed nav_pci info */
+/* This is not perfectly in sync with the display, but all the same, */
+/* much closer than doing it at the input stage. */
+/* returns a bool for error/success.*/
+static int spudec_get_interact_info (spu_decoder_t *this_gen, void *data) {
+ spudec_decoder_t *this = (spudec_decoder_t *) this_gen;
+ /*printf("get_interact_info() called\n");*/
+ if (!this || !data)
+ return 0;
+
+ /*printf("get_interact_info() coping nav_pci\n");*/
+ pthread_mutex_lock(&this->nav_pci_lock);
+ spudec_update_nav(this);
+ memcpy(data, &this->pci_cur.pci, sizeof(pci_t) );
+ pthread_mutex_unlock(&this->nav_pci_lock);
+ return 1;
+
+}
+
+static void spudec_set_button (spu_decoder_t *this_gen, int32_t button, int32_t show) {
+ spudec_decoder_t *this = (spudec_decoder_t *) this_gen;
+ /* This function will move to video_overlay
+ * when video_overlay does menus */
+
+ video_overlay_manager_t *ovl_manager;
+ video_overlay_event_t *overlay_event = calloc(1, sizeof(video_overlay_event_t));
+ vo_overlay_t *overlay = calloc(1, sizeof(vo_overlay_t));
+
+ /* FIXME: Watch out for threads. We should really put a lock on this
+ * because events is a different thread than decode_data */
+
+ if( this->menu_handle < 0 ) {
+ if (this->stream->video_out) {
+ ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out);
+ this->menu_handle = ovl_manager->get_handle(ovl_manager,1);
+ }
+ }
+#ifdef LOG_BUTTON
+ printf ("libspudec:xine_decoder.c:spudec_event_listener:this=%p\n",this);
+ printf ("libspudec:xine_decoder.c:spudec_event_listener:this->menu_handle=%d\n",this->menu_handle);
+#endif
+ if(this->menu_handle < 0) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "Menu handle alloc failed. No more overlays objects available. Only %d at once please.",
+ MAX_OBJECTS);
+ free(overlay_event);
+ free(overlay);
+ return;
+ }
+
+ if (show > 0) {
+#ifdef LOG_NAV
+ fprintf (stderr,"libspudec:xine_decoder.c:spudec_event_listener:buttonN = %u show=%d\n",
+ button,
+ show);
+#endif
+ this->buttonN = button;
+ if (this->button_filter != 1) {
+#ifdef LOG_BUTTON
+ fprintf (stdout,"libspudec:xine_decoder.c:spudec_event_listener:buttonN updates not allowed\n");
+#endif
+ /* Only update highlight is the menu will let us */
+ free(overlay_event);
+ free(overlay);
+ return;
+ }
+ if (show == 2) {
+ this->button_filter = 2;
+ }
+ pthread_mutex_lock(&this->nav_pci_lock);
+ spudec_update_nav(this);
+ overlay_event->object.handle = this->menu_handle;
+ overlay_event->object.pts = this->pci_cur.pci.hli.hl_gi.hli_s_ptm;
+ overlay_event->object.overlay=overlay;
+ overlay_event->event_type = OVERLAY_EVENT_MENU_BUTTON;
+#ifdef LOG_BUTTON
+ fprintf(stderr, "libspudec:Button Overlay\n");
+#endif
+ spudec_copy_nav_to_overlay(this->stream->xine, &this->pci_cur.pci, this->state.clut,
+ this->buttonN, show-1, overlay, &this->overlay );
+ pthread_mutex_unlock(&this->nav_pci_lock);
+ } else {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libspudec:xine_decoder.c:spudec_event_listener:HIDE ????\n");
+ printf("We dropped out here for some reason");
+ _x_abort();
+ overlay_event->object.handle = this->menu_handle;
+ overlay_event->event_type = OVERLAY_EVENT_HIDE;
+ }
+ overlay_event->vpts = 0;
+ if (this->stream->video_out) {
+ ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out);
+#ifdef LOG_BUTTON
+ fprintf(stderr, "libspudec: add_event type=%d : current time=%lld, spu vpts=%lli\n",
+ overlay_event->event_type,
+ this->stream->xine->clock->get_current_time(this->stream->xine->clock),
+ overlay_event->vpts);
+#endif
+ ovl_manager->add_event (ovl_manager, (void *)overlay_event);
+ free(overlay_event);
+ free(overlay);
+ } else {
+ free(overlay_event);
+ free(overlay);
+ }
+ return;
+}
+
+static spu_decoder_t *open_plugin (spu_decoder_class_t *class_gen, xine_stream_t *stream) {
+
+ spudec_decoder_t *this ;
+
+ this = (spudec_decoder_t *) calloc(1, sizeof (spudec_decoder_t));
+
+ this->spu_decoder.decode_data = spudec_decode_data;
+ this->spu_decoder.reset = spudec_reset;
+ this->spu_decoder.discontinuity = spudec_discontinuity;
+ this->spu_decoder.dispose = spudec_dispose;
+ this->spu_decoder.get_interact_info = spudec_get_interact_info;
+ this->spu_decoder.set_button = spudec_set_button;
+ this->stream = stream;
+ this->class = (spudec_class_t *) class_gen;
+
+ this->menu_handle = -1;
+ this->buttonN = 1;
+ this->event.object.overlay = calloc(1, sizeof(vo_overlay_t));
+
+ pthread_mutex_init(&this->nav_pci_lock, NULL);
+ this->pci_cur.pci.hli.hl_gi.hli_ss = 0;
+ this->pci_cur.next = NULL;
+
+ this->ovl_caps = stream->video_out->get_capabilities(stream->video_out);
+ this->output_open = 0;
+ this->last_event_vpts = 0;
+
+ int i;
+ for (i=0; i < MAX_STREAMS; i++) {
+ this->spudec_stream_state[i].ra_seq.complete = 1;
+ this->spudec_stream_state[i].overlay_handle = -1;
+ }
+
+/* FIXME:Do we really need a default clut? */
+ xine_fast_memcpy(this->state.clut, default_clut, sizeof(this->state.clut));
+ this->state.need_clut = 1;
+ this->state.vobsub = 0;
+
+ return &this->spu_decoder;
+}
+
+static void *init_plugin (xine_t *xine, void *data) {
+
+ spudec_class_t *this;
+
+ this = calloc(1, sizeof (spudec_class_t));
+
+ this->decoder_class.open_plugin = open_plugin;
+ this->decoder_class.identifier = "spudec";
+ this->decoder_class.description = N_("DVD/VOB SPU decoder plugin");
+ this->decoder_class.dispose = default_spu_decoder_class_dispose;
+
+ lprintf ("libspudec:init_plugin called\n");
+ return this;
+}
+
+/* plugin catalog information */
+static const uint32_t supported_types[] = { BUF_SPU_DVD, 0 };
+
+static const decoder_info_t dec_info_data = {
+ supported_types, /* supported types */
+ 5 /* priority */
+};
+
+const plugin_info_t xine_plugin_info[] EXPORTED = {
+ /* type, API, "name", version, special_info, init_function */
+ { PLUGIN_SPU_DECODER, 17, "spudec", XINE_VERSION_CODE, &dec_info_data, &init_plugin },
+ { PLUGIN_NONE, 0, "", 0, NULL, NULL }
+};
diff --git a/src/spu_dec/spudec.c b/src/spu_dec/spudec.c
new file mode 100644
index 000000000..fc305b864
--- /dev/null
+++ b/src/spu_dec/spudec.c
@@ -0,0 +1,1034 @@
+/*
+ * Copyright (C) 2002-2004 the xine project
+ *
+ * Copyright (C) James Courtier-Dutton James@superbug.demon.co.uk - July 2001
+ *
+ * spu.c - converts DVD subtitles to an XPM image
+ *
+ * Mostly based on hard work by:
+ *
+ * Copyright (C) 2000 Samuel Hocevar <sam@via.ecp.fr>
+ * and Michel Lespinasse <walken@via.ecp.fr>
+ *
+ * Lots of rearranging by:
+ * Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
+ * Thomas Mirlacher <dent@cosy.sbg.ac.at>
+ * implemented reassembling
+ * cleaner implementation of SPU are saving
+ * overlaying (proof of concept for now)
+ * ... and yes, it works now with oms
+ * added tranparency (provided by the SPU hdr)
+ * changed structures for easy porting to MGAs DVD mode
+ * This file is part of xine
+ * This file was originally part of the OMS program.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <xine/xine_internal.h>
+#include <xine/xineutils.h>
+#include <xine/buffer.h>
+#include "xine-engine/bswap.h"
+#ifdef HAVE_DVDNAV
+# ifdef HAVE_DVDNAV_NAVTYPES_H
+# include <dvdnav/nav_read.h>
+# include <dvdnav/nav_print.h>
+# else
+# include <dvdread/nav_read.h>
+# include <dvdread/nav_print.h>
+# endif
+#else
+# include "nav_read.h"
+# include "nav_print.h"
+#endif
+
+#include "spudec.h"
+
+/*
+#define LOG_DEBUG 1
+#define LOG_BUTTON 1
+#define LOG_NAV 1
+*/
+
+static void spudec_do_commands (xine_t *xine, spudec_state_t *state, spudec_seq_t* seq, vo_overlay_t *ovl);
+static void spudec_draw_picture (xine_t *xine, spudec_state_t *state, spudec_seq_t* seq, vo_overlay_t *ovl);
+static void spudec_discover_clut (xine_t *xine, spudec_state_t *state, vo_overlay_t *ovl);
+#ifdef LOG_DEBUG
+static void spudec_print_overlay( vo_overlay_t *overlay );
+#endif
+
+void spudec_decode_nav(spudec_decoder_t *this, buf_element_t *buf) {
+ uint8_t *p;
+ uint32_t packet_len;
+ uint32_t stream_id;
+ uint32_t header_len;
+ pci_t pci;
+ dsi_t dsi;
+ video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out);
+
+ p = buf->content;
+ if (p[0] || p[1] || (p[2] != 1)) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libspudec:spudec_decode_nav:nav demux error! %02x %02x %02x (should be 0x000001) \n",p[0],p[1],p[2]);
+ return;
+ }
+
+ packet_len = p[4] << 8 | p[5];
+ stream_id = p[3];
+
+ header_len = 6;
+ p += header_len;
+
+ if (stream_id == 0xbf) { /* Private stream 2 */
+/* int i;
+ * for(i=0;i<80;i++) {
+ * printf("%02x ",p[i]);
+ * }
+ * printf("\n p[0]=0x%02x\n",p[0]);
+ */
+ if(p[0] == 0x00) {
+#ifdef LOG_NAV
+ printf("libspudec:nav_PCI\n");
+#endif
+ navRead_PCI(&pci, p+1);
+#ifdef LOG_NAV
+ printf("libspudec:nav:hli_ss=%u, hli_s_ptm=%u, hli_e_ptm=%u, btn_sl_e_ptm=%u pts=%lli\n",
+ pci.hli.hl_gi.hli_ss,
+ pci.hli.hl_gi.hli_s_ptm,
+ pci.hli.hl_gi.hli_e_ptm,
+ pci.hli.hl_gi.btn_se_e_ptm,
+ buf->pts);
+ printf("libspudec:nav:btn_sn/ofn=%u, btn_ns=%u, fosl_btnn=%u, foac_btnn=%u\n",
+ pci.hli.hl_gi.btn_ofn, pci.hli.hl_gi.btn_ns,
+ pci.hli.hl_gi.fosl_btnn, pci.hli.hl_gi.foac_btnn);
+ printf("btngr_ns %d\n", pci.hli.hl_gi.btngr_ns);
+ printf("btngr%d_dsp_ty 0x%02x\n", 1, pci.hli.hl_gi.btngr1_dsp_ty);
+ printf("btngr%d_dsp_ty 0x%02x\n", 2, pci.hli.hl_gi.btngr2_dsp_ty);
+ printf("btngr%d_dsp_ty 0x%02x\n", 3, pci.hli.hl_gi.btngr3_dsp_ty);
+ //navPrint_PCI(&pci);
+ //navPrint_PCI_GI(&pci.pci_gi);
+ //navPrint_NSML_AGLI(&pci.nsml_agli);
+ //navPrint_HLI(&pci.hli);
+ //navPrint_HL_GI(&pci.hli.hl_gi, & btngr_ns, & btn_ns);
+#endif
+ }
+
+ p += packet_len;
+
+ /* We should now have a DSI packet. */
+ /* We don't need anything from the DSI packet here. */
+ if(p[6] == 0x01) {
+ packet_len = p[4] << 8 | p[5];
+ p += 6;
+#ifdef LOG_NAV
+ printf("NAV DSI packet\n");
+#endif
+ navRead_DSI(&dsi, p+1);
+
+// self->vobu_start = self->dsi.dsi_gi.nv_pck_lbn;
+// self->vobu_length = self->dsi.dsi_gi.vobu_ea;
+ }
+ }
+
+ /* NAV packets contain start and end presentation timestamps, which tell the
+ * application, when the highlight information in the NAV is supposed to be valid.
+ * We handle these timestamps only in a very stripped-down way: We keep a list
+ * of NAV packets (or better: the PCI part of them), tagged with a VPTS timestamp
+ * telling, when the NAV should be processed. However, we only enqueue a new node
+ * into this list, when we receive new highlight information during an already
+ * showing menu. This happens very rarerly on common DVDs, so it is of low impact.
+ * And we only check for processing of queued entries at some prominent
+ * locations in this SPU decoder. Since presentation timestamps rarely solve a real
+ * purpose on most DVDs, this is ok compared to the full-blown solution, which would
+ * require a separate thread managing the queue all the time. */
+ pthread_mutex_lock(&this->nav_pci_lock);
+ switch (pci.hli.hl_gi.hli_ss) {
+ case 0:
+ /* No Highlight information for this VOBU */
+ if ( this->pci_cur.pci.hli.hl_gi.hli_ss == 1) {
+ /* Hide menu spu between menus */
+#ifdef LOG_BUTTON
+ printf("libspudec:nav:SHOULD HIDE SPU here\n");
+#endif
+ if( this->menu_handle < 0 ) {
+ this->menu_handle = ovl_manager->get_handle(ovl_manager,1);
+ }
+ if( this->menu_handle >= 0 ) {
+ this->event.object.handle = this->menu_handle;
+ this->event.event_type = OVERLAY_EVENT_HIDE;
+ /* hide menu right now */
+ this->event.vpts = 0;
+ ovl_manager->add_event(ovl_manager, (void *)&this->event);
+ } else {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, "libspudec: No video_overlay handles left for menu\n");
+ }
+ }
+ spudec_clear_nav_list(this);
+ xine_fast_memcpy(&this->pci_cur.pci, &pci, sizeof(pci_t));
+ /* incoming SPUs will be plain subtitles */
+ this->event.object.object_type = 0;
+ if (this->button_filter) {
+ /* we possibly had buttons before, so we update the UI info */
+ xine_event_t event;
+ xine_ui_data_t data;
+
+ event.type = XINE_EVENT_UI_NUM_BUTTONS;
+ event.data = &data;
+ event.data_length = sizeof(data);
+ data.num_buttons = 0;
+
+ xine_event_send(this->stream, &event);
+ }
+ this->button_filter=0;
+
+ break;
+ case 1:
+ /* All New Highlight information for this VOBU */
+ if (this->pci_cur.pci.hli.hl_gi.hli_ss != 0 &&
+ pci.hli.hl_gi.hli_s_ptm > this->pci_cur.pci.hli.hl_gi.hli_s_ptm) {
+ pci_node_t *node = &this->pci_cur;
+#ifdef LOG_DEBUG
+ printf("libspudec: allocating new PCI node for hli_s_ptm %d\n", pci.hli.hl_gi.hli_s_ptm);
+#endif
+ /* append PCI at the end of the list */
+ while (node->next) node = node->next;
+ node->next = malloc(sizeof(pci_node_t));
+ node->next->vpts = this->stream->metronom->got_spu_packet(this->stream->metronom, pci.hli.hl_gi.hli_s_ptm);
+ node->next->next = NULL;
+ xine_fast_memcpy(&node->next->pci, &pci, sizeof(pci_t));
+ } else {
+ spudec_clear_nav_list(this);
+ /* menu ahead, remember PCI for later use */
+ xine_fast_memcpy(&this->pci_cur.pci, &pci, sizeof(pci_t));
+ spudec_process_nav(this);
+ }
+ break;
+ case 2:
+ /* Use Highlight information from previous VOBU */
+ if (this->pci_cur.next) {
+ /* apply changes to last enqueued NAV */
+ pci_node_t *node = this->pci_cur.next;
+ while (node->next) node = node->next;
+ node->pci.pci_gi.vobu_s_ptm = pci.pci_gi.vobu_s_ptm;
+ node->pci.pci_gi.vobu_e_ptm = pci.pci_gi.vobu_e_ptm;
+ node->pci.pci_gi.vobu_se_e_ptm = pci.pci_gi.vobu_se_e_ptm;
+ spudec_update_nav(this);
+ } else {
+ this->pci_cur.pci.pci_gi.vobu_s_ptm = pci.pci_gi.vobu_s_ptm;
+ this->pci_cur.pci.pci_gi.vobu_e_ptm = pci.pci_gi.vobu_e_ptm;
+ this->pci_cur.pci.pci_gi.vobu_se_e_ptm = pci.pci_gi.vobu_se_e_ptm;
+ }
+ break;
+ case 3:
+ /* Use Highlight information from previous VOBU except commands, which come from this VOBU */
+ if (this->pci_cur.next) {
+ /* apply changes to last enqueued NAV */
+ pci_node_t *node = this->pci_cur.next;
+ while (node->next) node = node->next;
+ node->pci.pci_gi.vobu_s_ptm = pci.pci_gi.vobu_s_ptm;
+ node->pci.pci_gi.vobu_e_ptm = pci.pci_gi.vobu_e_ptm;
+ node->pci.pci_gi.vobu_se_e_ptm = pci.pci_gi.vobu_se_e_ptm;
+ /* FIXME: Add command copying here */
+ spudec_update_nav(this);
+ } else {
+ this->pci_cur.pci.pci_gi.vobu_s_ptm = pci.pci_gi.vobu_s_ptm;
+ this->pci_cur.pci.pci_gi.vobu_e_ptm = pci.pci_gi.vobu_e_ptm;
+ this->pci_cur.pci.pci_gi.vobu_se_e_ptm = pci.pci_gi.vobu_se_e_ptm;
+ /* FIXME: Add command copying here */
+ }
+ break;
+ default:
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libspudec: unknown pci.hli.hl_gi.hli_ss = %d\n", pci.hli.hl_gi.hli_ss );
+ break;
+ }
+ pthread_mutex_unlock(&this->nav_pci_lock);
+ return;
+}
+
+void spudec_clear_nav_list(spudec_decoder_t *this)
+{
+ while (this->pci_cur.next) {
+ pci_node_t *node = this->pci_cur.next->next;
+ free(this->pci_cur.next);
+ this->pci_cur.next = node;
+ }
+ /* invalidate current timestamp */
+ this->pci_cur.pci.hli.hl_gi.hli_s_ptm = (uint32_t)-1;
+}
+
+void spudec_update_nav(spudec_decoder_t *this)
+{
+ metronom_clock_t *clock = this->stream->xine->clock;
+
+ if (this->pci_cur.next && this->pci_cur.next->vpts <= clock->get_current_time(clock)) {
+ pci_node_t *node = this->pci_cur.next;
+ xine_fast_memcpy(&this->pci_cur, this->pci_cur.next, sizeof(pci_node_t));
+ spudec_process_nav(this);
+ free(node);
+ }
+}
+
+void spudec_process_nav(spudec_decoder_t *this)
+{
+ /* incoming SPUs will be menus */
+ this->event.object.object_type = 1;
+ if (!this->button_filter) {
+ /* we possibly entered a menu, so we update the UI button info */
+ xine_event_t event;
+ xine_ui_data_t data;
+
+ event.type = XINE_EVENT_UI_NUM_BUTTONS;
+ event.data = &data;
+ event.data_length = sizeof(data);
+ data.num_buttons = this->pci_cur.pci.hli.hl_gi.btn_ns;
+
+ xine_event_send(this->stream, &event);
+ }
+ this->button_filter=1;
+}
+
+void spudec_reassembly (xine_t *xine, spudec_seq_t *seq, uint8_t *pkt_data, u_int pkt_len)
+{
+#ifdef LOG_DEBUG
+ printf ("libspudec: seq->complete = %d\n", seq->complete);
+ printf("libspudec:1: seq->ra_offs = %d, seq->seq_len = %d, seq->buf_len = %d, seq->buf=%p\n",
+ seq->ra_offs,
+ seq->seq_len,
+ seq->buf_len,
+ seq->buf);
+#endif
+ if (seq->complete) {
+ seq->seq_len = (((uint32_t)pkt_data[0])<<8) | pkt_data[1];
+ seq->cmd_offs = (((uint32_t)pkt_data[2])<<8) | pkt_data[3];
+ if (seq->cmd_offs >= seq->seq_len) {
+ xprintf(xine, XINE_VERBOSITY_DEBUG, "libspudec:faulty stream\n");
+ seq->broken = 1;
+ }
+ if (seq->buf_len < seq->seq_len) {
+ seq->buf_len = seq->seq_len;
+#ifdef LOG_DEBUG
+ printf ("spu: MALLOC1: seq->buf %p, len=%d\n", seq->buf,seq->buf_len);
+#endif
+ if (seq->buf) {
+ free(seq->buf);
+ seq->buf = NULL;
+ }
+ seq->buf = malloc(seq->buf_len);
+#ifdef LOG_DEBUG
+ printf ("spu: MALLOC2: seq->buf %p, len=%d\n", seq->buf,seq->buf_len);
+#endif
+
+ }
+ seq->ra_offs = 0;
+
+#ifdef LOG_DEBUG
+ printf ("spu: buf_len: %d\n", seq->buf_len);
+ printf ("spu: cmd_off: %d\n", seq->cmd_offs);
+#endif
+ }
+
+#ifdef LOG_DEBUG
+ printf("libspudec:2: seq->ra_offs = %d, seq->seq_len = %d, seq->buf_len = %d, seq->buf=%p\n",
+ seq->ra_offs,
+ seq->seq_len,
+ seq->buf_len,
+ seq->buf);
+#endif
+ if (seq->ra_offs < seq->seq_len) {
+ if (seq->ra_offs + pkt_len > seq->seq_len)
+ pkt_len = seq->seq_len - seq->ra_offs;
+ memcpy (seq->buf + seq->ra_offs, pkt_data, pkt_len);
+ seq->ra_offs += pkt_len;
+ } else {
+ xprintf(xine, XINE_VERBOSITY_DEBUG, "libspudec:faulty stream\n");
+ seq->broken = 1;
+ }
+
+ if (seq->ra_offs == seq->seq_len) {
+ seq->finished = 0;
+ seq->complete = 1;
+ return; /* sequence ready */
+ }
+ seq->complete = 0;
+ return;
+}
+
+void spudec_process (spudec_decoder_t *this, int stream_id) {
+ spudec_seq_t *cur_seq;
+ video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out);
+ int pending = 1;
+ cur_seq = &this->spudec_stream_state[stream_id].ra_seq;
+
+#ifdef LOG_DEBUG
+ printf ("spu: Found SPU from stream %d pts=%lli vpts=%lli\n",stream_id,
+ this->spudec_stream_state[stream_id].pts,
+ this->spudec_stream_state[stream_id].vpts);
+#endif
+ this->state.cmd_ptr = cur_seq->buf + cur_seq->cmd_offs;
+ this->state.modified = 1; /* Only draw picture if = 1 on first event of SPU */
+ this->state.visible = OVERLAY_EVENT_SHOW;
+ this->state.forced_display = 0; /* 0 - No value, 1 - Forced Display. */
+ this->state.delay = 0;
+ cur_seq->finished=0;
+
+ do {
+ if (!(cur_seq->finished) ) {
+ pci_node_t *node;
+
+ /* spu_channel is now set based on whether we are in the menu or not. */
+ /* Bit 7 is set if only forced display SPUs should be shown */
+ if ( (this->stream->spu_channel & 0x1f) != stream_id ) {
+#ifdef LOG_DEBUG
+ printf ("spu: Dropping SPU channel %d. Not selected stream_id\n", stream_id);
+#endif
+ return;
+ }
+ /* parse SPU command sequence, this will update forced_display, so it must come
+ * before the check for it */
+ spudec_do_commands(this->stream->xine, &this->state, cur_seq, &this->overlay);
+ /* FIXME: Check for Forced-display or subtitle stream
+ * For subtitles, open event.
+ * For menus, store it for later.
+ */
+ if (cur_seq->broken) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, "libspudec: dropping broken SPU\n");
+ cur_seq->broken = 0;
+ return;
+ }
+ if ( (this->state.forced_display == 0) && (this->stream->spu_channel & 0x80) ) {
+#ifdef LOG_DEBUG
+ printf ("spu: Dropping SPU channel %d. Only allow forced display SPUs\n", stream_id);
+#endif
+ return;
+ }
+
+#ifdef LOG_DEBUG
+ spudec_print_overlay( &this->overlay );
+ printf ("spu: forced display:%s\n", this->state.forced_display ? "Yes" : "No" );
+#endif
+ pthread_mutex_lock(&this->nav_pci_lock);
+ /* search for a PCI that matches this SPU's PTS */
+ for (node = &this->pci_cur; node; node = node->next)
+ if (node->pci.hli.hl_gi.hli_s_ptm == this->spudec_stream_state[stream_id].pts)
+ break;
+ if (node) {
+ if (this->state.visible == OVERLAY_EVENT_HIDE) {
+ /* menus are hidden via nav packet decoding, not here */
+ /* FIXME: James is not sure about this solution and may want to look this over.
+ * I'm commiting it, because I haven't found a disc it breaks, but it fixes
+ * some instead. Michael Roitzsch */
+ pthread_mutex_unlock(&this->nav_pci_lock);
+ continue;
+ }
+ if (node->pci.hli.hl_gi.fosl_btnn > 0) {
+ xine_event_t event;
+
+ this->buttonN = node->pci.hli.hl_gi.fosl_btnn;
+ event.type = XINE_EVENT_INPUT_BUTTON_FORCE;
+ event.stream = this->stream;
+ event.data = &this->buttonN;
+ event.data_length = sizeof(this->buttonN);
+ xine_event_send(this->stream, &event);
+ }
+#ifdef LOG_BUTTON
+ fprintf(stderr, "libspudec:Full Overlay\n");
+#endif
+ if (!spudec_copy_nav_to_overlay(this->stream->xine,
+ &node->pci, this->state.clut,
+ this->buttonN, 0, &this->overlay, &this->overlay)) {
+ /* current button does not exist -> use another one */
+ xine_event_t event;
+
+ if (this->buttonN > node->pci.hli.hl_gi.btn_ns)
+ this->buttonN = node->pci.hli.hl_gi.btn_ns;
+ else
+ this->buttonN = 1;
+ event.type = XINE_EVENT_INPUT_BUTTON_FORCE;
+ event.stream = this->stream;
+ event.data = &this->buttonN;
+ event.data_length = sizeof(this->buttonN);
+ xine_event_send(this->stream, &event);
+ spudec_copy_nav_to_overlay(this->stream->xine,
+ &node->pci, this->state.clut,
+ this->buttonN, 0, &this->overlay, &this->overlay);
+ }
+ } else {
+ /* Subtitle and not a menu button */
+ int i;
+ for (i = 0;i < 4; i++) {
+ this->overlay.hili_color[i] = this->overlay.color[i];
+ this->overlay.hili_trans[i] = this->overlay.trans[i];
+ }
+ }
+ pthread_mutex_unlock(&this->nav_pci_lock);
+
+ if ((this->state.modified) ) {
+ spudec_draw_picture(this->stream->xine, &this->state, cur_seq, &this->overlay);
+ }
+
+ if (this->state.need_clut) {
+ spudec_discover_clut(this->stream->xine, &this->state, &this->overlay);
+ }
+
+ if (this->state.vobsub) {
+ int width, height;
+ int64_t duration;
+
+ /*
+ * vobsubs are usually played with a scaled-down stream (not full DVD
+ * resolution), therefore we should try to realign it.
+ */
+
+ this->stream->video_out->status(this->stream->video_out, NULL,
+ &width, &height, &duration );
+
+ this->overlay.x = (width - this->overlay.width) / 2;
+ this->overlay.y = height - this->overlay.height;
+ }
+
+ /* Subtitle */
+ if( this->menu_handle < 0 ) {
+ this->menu_handle = ovl_manager->get_handle(ovl_manager,1);
+ }
+
+ if( this->menu_handle < 0 ) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libspudec: No video_overlay handles left for menu\n");
+ return;
+ }
+ this->event.object.handle = this->menu_handle;
+ this->event.object.pts = this->spudec_stream_state[stream_id].pts;
+
+ xine_fast_memcpy(this->event.object.overlay,
+ &this->overlay,
+ sizeof(vo_overlay_t));
+ this->overlay.rle=NULL;
+ /* For force display menus */
+ //if ( !(this->state.visible) ) {
+ // this->state.visible = OVERLAY_EVENT_SHOW;
+ //}
+
+ this->event.event_type = this->state.visible;
+ /*
+ printf("spu event %d handle: %d vpts: %lli\n", this->event.event_type,
+ this->event.object.handle, this->event.vpts );
+ */
+
+ this->event.vpts = this->spudec_stream_state[stream_id].vpts+(this->state.delay*1000);
+
+ /* Keep all the events in the correct order. */
+ /* This corrects for errors during estimation around discontinuity */
+ if( this->event.vpts < this->last_event_vpts ) {
+ this->event.vpts = this->last_event_vpts + 1;
+ }
+ this->last_event_vpts = this->event.vpts;
+
+#ifdef LOG_BUTTON
+ fprintf(stderr, "libspudec: add_event type=%d : current time=%lld, spu vpts=%lli\n",
+ this->event.event_type,
+ this->stream->xine->clock->get_current_time(this->stream->xine->clock),
+ this->event.vpts);
+#endif
+ ovl_manager->add_event(ovl_manager, (void *)&this->event);
+ } else {
+ pending = 0;
+ }
+ } while (pending);
+
+}
+
+#define CMD_SPU_FORCE_DISPLAY 0x00
+#define CMD_SPU_SHOW 0x01
+#define CMD_SPU_HIDE 0x02
+#define CMD_SPU_SET_PALETTE 0x03
+#define CMD_SPU_SET_ALPHA 0x04
+#define CMD_SPU_SET_SIZE 0x05
+#define CMD_SPU_SET_PXD_OFFSET 0x06
+#define CMD_SPU_WIPE 0x07 /* Not currently implemented */
+#define CMD_SPU_EOF 0xff
+
+static void spudec_do_commands(xine_t *xine, spudec_state_t *state, spudec_seq_t* seq, vo_overlay_t *ovl)
+{
+ uint8_t *buf = state->cmd_ptr;
+ uint8_t *next_seq;
+ int32_t param_length;
+
+#ifdef LOG_DEBUG
+ printf ("spu: SPU DO COMMANDS\n");
+#endif
+
+ state->delay = (buf[0] << 8) + buf[1];
+#ifdef LOG_DEBUG
+ printf ("spu: \tdelay=%d\n",state->delay);
+#endif
+ next_seq = seq->buf + (buf[2] << 8) + buf[3];
+ buf += 4;
+#ifdef LOG_DEBUG
+ printf ("spu: \tnext_seq=%d\n",next_seq - seq->buf);
+#endif
+
+/* if next equals current, this is the last one
+ */
+ if (state->cmd_ptr >= next_seq)
+ next_seq = seq->buf + seq->seq_len; /* allow to run until end */
+
+ state->cmd_ptr = next_seq;
+
+ while (buf < next_seq && *buf != CMD_SPU_EOF) {
+ switch (*buf) {
+ case CMD_SPU_SHOW: /* show subpicture */
+#ifdef LOG_DEBUG
+ printf ("spu: \tshow subpicture\n");
+#endif
+ state->visible = OVERLAY_EVENT_SHOW;
+ buf++;
+ break;
+
+ case CMD_SPU_HIDE: /* hide subpicture */
+#ifdef LOG_DEBUG
+ printf ("spu: \thide subpicture\n");
+#endif
+ state->visible = OVERLAY_EVENT_HIDE;
+ buf++;
+ break;
+
+ case CMD_SPU_SET_PALETTE: { /* CLUT */
+ spudec_clut_t *clut = (spudec_clut_t *) (buf+1);
+
+ state->cur_colors[3] = clut->entry0;
+ state->cur_colors[2] = clut->entry1;
+ state->cur_colors[1] = clut->entry2;
+ state->cur_colors[0] = clut->entry3;
+
+/* This is a bit out of context for now */
+ ovl->color[3] = state->clut[clut->entry0];
+ ovl->color[2] = state->clut[clut->entry1];
+ ovl->color[1] = state->clut[clut->entry2];
+ ovl->color[0] = state->clut[clut->entry3];
+
+#ifdef LOG_DEBUG
+ printf ("spu: \tclut [%x %x %x %x]\n",
+ ovl->color[0], ovl->color[1], ovl->color[2], ovl->color[3]);
+ printf ("spu: \tclut base [%x %x %x %x]\n",
+ clut->entry0, clut->entry1, clut->entry2, clut->entry3);
+#endif
+ state->modified = 1;
+ buf += 3;
+ break;
+ }
+ case CMD_SPU_SET_ALPHA: { /* transparency palette */
+ spudec_clut_t *trans = (spudec_clut_t *) (buf+1);
+/* This should go into state for now */
+
+ ovl->trans[3] = trans->entry0;
+ ovl->trans[2] = trans->entry1;
+ ovl->trans[1] = trans->entry2;
+ ovl->trans[0] = trans->entry3;
+
+#ifdef LOG_DEBUG
+ printf ("spu: \ttrans [%d %d %d %d]\n",
+ ovl->trans[0], ovl->trans[1], ovl->trans[2], ovl->trans[3]);
+#endif
+ state->modified = 1;
+ buf += 3;
+ break;
+ }
+
+ case CMD_SPU_SET_SIZE: /* image coordinates */
+/* state->o_left = (buf[1] << 4) | (buf[2] >> 4);
+ state->o_right = (((buf[2] & 0x0f) << 8) | buf[3]);
+
+ state->o_top = (buf[4] << 4) | (buf[5] >> 4);
+ state->o_bottom = (((buf[5] & 0x0f) << 8) | buf[6]);
+ */
+ ovl->x = (buf[1] << 4) | (buf[2] >> 4);
+ ovl->y = (buf[4] << 4) | (buf[5] >> 4);
+ ovl->width = (((buf[2] & 0x0f) << 8) | buf[3]) - ovl->x + 1;
+ ovl->height = (((buf[5] & 0x0f) << 8) | buf[6]) - ovl->y + 1;
+ ovl->hili_top = -1;
+ ovl->hili_bottom = -1;
+ ovl->hili_left = -1;
+ ovl->hili_right = -1;
+
+#ifdef LOG_DEBUG
+ printf ("spu: \tx = %d y = %d width = %d height = %d\n",
+ ovl->x, ovl->y, ovl->width, ovl->height );
+#endif
+ state->modified = 1;
+ buf += 7;
+ break;
+
+ case CMD_SPU_SET_PXD_OFFSET: /* image top[0] field / image bottom[1] field*/
+ state->field_offs[0] = (((u_int)buf[1]) << 8) | buf[2];
+ state->field_offs[1] = (((u_int)buf[3]) << 8) | buf[4];
+
+#ifdef LOG_DEBUG
+ printf ("spu: \toffset[0] = %d offset[1] = %d\n",
+ state->field_offs[0], state->field_offs[1]);
+#endif
+
+ if ((state->field_offs[0] >= seq->seq_len) ||
+ (state->field_offs[1] >= seq->seq_len)) {
+ xprintf(xine, XINE_VERBOSITY_DEBUG, "libspudec:faulty stream\n");
+ seq->broken = 1;
+ }
+ state->modified = 1;
+ buf += 5;
+ break;
+
+ case CMD_SPU_WIPE:
+#ifdef LOG_DEBUG
+ printf ("libspudec: \tSPU_WIPE not implemented yet\n");
+#endif
+ param_length = (buf[1] << 8) | (buf[2]);
+ buf += 1 + param_length;
+ break;
+
+ case CMD_SPU_FORCE_DISPLAY:
+#ifdef LOG_DEBUG
+ printf ("libspudec: \tForce Display/Menu\n");
+#endif
+ state->forced_display = 1;
+ buf++;
+ break;
+
+ default:
+ xprintf(xine, XINE_VERBOSITY_DEBUG, "libspudec: unknown seqence command (%02x)\n", buf[0]);
+ /* FIXME: SPU should be dropped, and buffers resynced */
+ buf = next_seq;
+ seq->broken = 1;
+ break;
+ }
+ }
+
+ if (next_seq >= seq->buf + seq->seq_len)
+ seq->finished = 1; /* last sub-sequence */
+}
+
+/* FIXME: Get rid of all these static values */
+static uint8_t *bit_ptr[2];
+static int field; // which field we are currently decoding
+static int put_x, put_y;
+
+static u_int get_bits (u_int bits)
+{
+ static u_int data;
+ static u_int bits_left;
+ u_int ret = 0;
+
+ if (!bits) { /* for realignment to next byte */
+ bits_left = 0;
+ }
+
+ while (bits) {
+ if (bits > bits_left) {
+ ret |= data << (bits - bits_left);
+ bits -= bits_left;
+
+ data = *bit_ptr[field]++;
+ bits_left = 8;
+ } else {
+ bits_left -= bits;
+ ret |= data >> (bits_left);
+ data &= (1 << bits_left) - 1;
+ bits = 0;
+ }
+ }
+
+ return ret;
+}
+
+static int spudec_next_line (vo_overlay_t *spu)
+{
+ get_bits (0); // byte align rle data
+
+ put_x = 0;
+ put_y++;
+ field ^= 1; // Toggle fields
+
+ if (put_y >= spu->height) {
+#ifdef LOG_DEBUG
+ printf ("spu: put_y >= spu->height\n");
+#endif
+ return -1;
+ }
+ return 0;
+}
+
+static void spudec_draw_picture (xine_t *xine, spudec_state_t *state, spudec_seq_t* seq, vo_overlay_t *ovl)
+{
+ rle_elem_t *rle;
+ field = 0;
+ bit_ptr[0] = seq->buf + state->field_offs[0];
+ bit_ptr[1] = seq->buf + state->field_offs[1];
+ put_x = put_y = 0;
+ get_bits (0); /* Reset/init bit code */
+
+/* ovl->x = state->o_left;
+ * ovl->y = state->o_top;
+ * ovl->width = state->o_right - state->o_left + 1;
+ * ovl->height = state->o_bottom - state->o_top + 1;
+
+ * ovl->hili_top = 0;
+ * ovl->hili_bottom = ovl->height - 1;
+ * ovl->hili_left = 0;
+ * ovl->hili_right = ovl->width - 1;
+ */
+
+ /* allocate for the worst case:
+ * - both fields running to the very end
+ * - 2 RLE elements per byte meaning single pixel RLE
+ */
+ ovl->data_size = ((seq->cmd_offs - state->field_offs[0]) +
+ (seq->cmd_offs - state->field_offs[1])) * 2 * sizeof(rle_elem_t);
+
+ if (ovl->rle) {
+ xprintf (xine, XINE_VERBOSITY_DEBUG,
+ "libspudec: spudec_draw_picture: ovl->rle is not empty!!!! It should be!!! "
+ "You should never see this message.\n");
+ free(ovl->rle);
+ ovl->rle=NULL;
+ }
+ ovl->rle = malloc(ovl->data_size);
+
+ state->modified = 0; /* mark as already processed */
+ rle = ovl->rle;
+#ifdef LOG_DEBUG
+ printf ("libspudec: Draw RLE=%p\n",rle);
+#endif
+
+ while (bit_ptr[1] < seq->buf + seq->cmd_offs) {
+ u_int len;
+ u_int vlc;
+
+ vlc = get_bits (4);
+ if (vlc < 0x0004) {
+ vlc = (vlc << 4) | get_bits (4);
+ if (vlc < 0x0010) {
+ vlc = (vlc << 4) | get_bits (4);
+ if (vlc < 0x0040) {
+ vlc = (vlc << 4) | get_bits (4);
+ }
+ }
+ }
+
+ len = vlc >> 2;
+
+ /* if len == 0 -> end sequence - fill to end of line */
+ if (len == 0)
+ len = ovl->width - put_x;
+
+ rle->len = len;
+ rle->color = vlc & 0x03;
+ rle++;
+ put_x += len;
+
+ if (put_x >= ovl->width) {
+ if (spudec_next_line (ovl) < 0)
+ break;
+ }
+ }
+
+ ovl->num_rle = rle - ovl->rle;
+ ovl->rgb_clut = 0;
+ ovl->unscaled = 0;
+#ifdef LOG_DEBUG
+ printf ("spu: Num RLE=%d\n",ovl->num_rle);
+ printf ("spu: Date size=%d\n",ovl->data_size);
+ printf ("spu: sizeof RLE=%d\n",sizeof(rle_elem_t));
+#endif
+}
+
+/* Heuristic to discover the colors used by the subtitles
+ and assign a "readable" pallete to them.
+ Currently looks for sequence of border-fg-border or
+ border1-border2-fg-border2-border1.
+ MINFOUND is the number of ocurrences threshold.
+*/
+#define MINFOUND 20
+static void spudec_discover_clut(xine_t *xine, spudec_state_t *state, vo_overlay_t *ovl)
+{
+ int bg,c;
+ int seqcolor[10];
+ int n,i;
+ rle_elem_t *rle;
+
+ int found[2][16] = { { 0, }, };
+
+ static const clut_t text_clut[] = {
+ CLUT_Y_CR_CB_INIT(0x80, 0x90, 0x80),
+ CLUT_Y_CR_CB_INIT(0x00, 0x90, 0x00),
+ CLUT_Y_CR_CB_INIT(0xff, 0x90, 0x00)
+ };
+
+ rle = ovl->rle;
+
+ /* this seems to be a problem somewhere else,
+ why rle is null? */
+ if( !rle )
+ return;
+
+ /* suppose the first and last pixels are bg */
+ if( rle[0].color != rle[ovl->num_rle-1].color )
+ return;
+
+ bg = rle[0].color;
+
+ i = 0;
+ for( n = 0; n < ovl->num_rle; n++ )
+ {
+ c = rle[n].color;
+
+ if( c == bg )
+ {
+ if( i == 3 && seqcolor[1] == seqcolor[3] )
+ {
+ found[0][seqcolor[2]]++;
+ if( found[0][seqcolor[2]] > MINFOUND )
+ {
+ memcpy(&state->clut[state->cur_colors[seqcolor[1]]], &text_clut[1],
+ sizeof(clut_t));
+ memcpy(&state->clut[state->cur_colors[seqcolor[2]]], &text_clut[2],
+ sizeof(clut_t));
+ ovl->color[seqcolor[1]] = state->clut[state->cur_colors[seqcolor[1]]];
+ ovl->color[seqcolor[2]] = state->clut[state->cur_colors[seqcolor[2]]];
+ state->need_clut = 0;
+ break;
+ }
+ }
+ if( i == 5 && seqcolor[1] == seqcolor[5]
+ && seqcolor[2] == seqcolor[4] )
+ {
+ found[1][seqcolor[3]]++;
+ if( found[1][seqcolor[3]] > MINFOUND )
+ {
+ memcpy(&state->clut[state->cur_colors[seqcolor[1]]], &text_clut[0],
+ sizeof(clut_t));
+ memcpy(&state->clut[state->cur_colors[seqcolor[2]]], &text_clut[1],
+ sizeof(clut_t));
+ memcpy(&state->clut[state->cur_colors[seqcolor[3]]], &text_clut[2],
+ sizeof(clut_t));
+ ovl->color[seqcolor[1]] = state->clut[state->cur_colors[seqcolor[1]]];
+ ovl->color[seqcolor[2]] = state->clut[state->cur_colors[seqcolor[2]]];
+ ovl->color[seqcolor[3]] = state->clut[state->cur_colors[seqcolor[3]]];
+ state->need_clut = 0;
+ break;
+ }
+ }
+ i = 0;
+ seqcolor[i] = c;
+ }
+ else if ( i < 6 )
+ {
+ i++;
+ seqcolor[i] = c;
+ }
+ }
+}
+
+#ifdef LOG_DEBUG
+static void spudec_print_overlay( vo_overlay_t *ovl ) {
+ printf ("spu: OVERLAY to show\n");
+ printf ("spu: \tx = %d y = %d width = %d height = %d\n",
+ ovl->x, ovl->y, ovl->width, ovl->height );
+ printf ("spu: \tclut [%x %x %x %x]\n",
+ ovl->color[0], ovl->color[1], ovl->color[2], ovl->color[3]);
+ printf ("spu: \ttrans [%d %d %d %d]\n",
+ ovl->trans[0], ovl->trans[1], ovl->trans[2], ovl->trans[3]);
+ printf ("spu: \tclip top=%d bottom=%d left=%d right=%d\n",
+ ovl->hili_top, ovl->hili_bottom, ovl->hili_left, ovl->hili_right);
+ printf ("spu: \tclip_clut [%x %x %x %x]\n",
+ ovl->hili_color[0], ovl->hili_color[1], ovl->hili_color[2], ovl->hili_color[3]);
+ printf ("spu: \thili_trans [%d %d %d %d]\n",
+ ovl->hili_trans[0], ovl->hili_trans[1], ovl->hili_trans[2], ovl->hili_trans[3]);
+ return;
+}
+#endif
+
+int spudec_copy_nav_to_overlay(xine_t *xine, pci_t* nav_pci, uint32_t* clut,
+ int32_t button, int32_t mode, vo_overlay_t * overlay, vo_overlay_t * base ) {
+ btni_t *button_ptr = NULL;
+ unsigned int btns_per_group;
+ int i;
+
+ if((button <= 0) || (button > nav_pci->hli.hl_gi.btn_ns))
+ return 0;
+
+ btns_per_group = 36 / nav_pci->hli.hl_gi.btngr_ns;
+
+ /* choose button group: we can always use a normal 4:3 or widescreen button group
+ * as long as xine blends the overlay before scaling the image to its aspect */
+ if (!button_ptr && nav_pci->hli.hl_gi.btngr_ns >= 1 && !(nav_pci->hli.hl_gi.btngr1_dsp_ty & 6))
+ button_ptr = &nav_pci->hli.btnit[0 * btns_per_group + button - 1];
+ if (!button_ptr && nav_pci->hli.hl_gi.btngr_ns >= 2 && !(nav_pci->hli.hl_gi.btngr2_dsp_ty & 6))
+ button_ptr = &nav_pci->hli.btnit[1 * btns_per_group + button - 1];
+ if (!button_ptr && nav_pci->hli.hl_gi.btngr_ns >= 3 && !(nav_pci->hli.hl_gi.btngr3_dsp_ty & 6))
+ button_ptr = &nav_pci->hli.btnit[2 * btns_per_group + button - 1];
+ if (!button_ptr) {
+ xprintf(xine, XINE_VERBOSITY_DEBUG,
+ "libspudec: No suitable menu button group found, using group 1.\n");
+ button_ptr = &nav_pci->hli.btnit[button - 1];
+ }
+
+ /* button areas in the nav packet are in screen coordinates,
+ * overlay clipping areas are in overlay coordinates;
+ * therefore we must subtract the display coordinates of the underlying overlay */
+ overlay->hili_left = (button_ptr->x_start > base->x) ? (button_ptr->x_start - base->x) : 0;
+ overlay->hili_top = (button_ptr->y_start > base->y) ? (button_ptr->y_start - base->y) : 0;
+ overlay->hili_right = (button_ptr->x_end > base->x) ? (button_ptr->x_end - base->x) : 0;
+ overlay->hili_bottom = (button_ptr->y_end > base->y) ? (button_ptr->y_end - base->y) : 0;
+ if(button_ptr->btn_coln != 0) {
+#ifdef LOG_BUTTON
+ fprintf(stderr, "libspudec: normal button clut\n");
+#endif
+ for (i = 0;i < 4; i++) {
+ overlay->hili_color[i] = clut[0xf & (nav_pci->hli.btn_colit.btn_coli[button_ptr->btn_coln-1][mode] >> (16 + 4*i))];
+ overlay->hili_trans[i] = 0xf & (nav_pci->hli.btn_colit.btn_coli[button_ptr->btn_coln-1][mode] >> (4*i));
+ }
+ } else {
+#ifdef LOG_BUTTON
+ fprintf(stderr, "libspudec: abnormal button clut\n");
+#endif
+ for (i = 0;i < 4; i++) {
+#ifdef LOG_BUTTON
+ printf("libspudec:btn_coln = 0, hili_color = colour\n");
+#endif
+ overlay->hili_color[i] = overlay->color[i];
+ overlay->hili_trans[i] = overlay->trans[i];
+ }
+ }
+
+ /* spudec_print_overlay( overlay ); */
+#ifdef LOG_BUTTON
+ printf("libspudec:xine_decoder.c:NAV to SPU pts match!\n");
+#endif
+
+ return 1;
+}
diff --git a/src/spu_dec/spudec.h b/src/spu_dec/spudec.h
new file mode 100644
index 000000000..225a87f13
--- /dev/null
+++ b/src/spu_dec/spudec.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2000-2004 the xine project
+ *
+ * Copyright (C) James Courtier-Dutton James@superbug.demon.co.uk - July 2001
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * This file was originally part of the OMS program.
+ */
+
+#ifndef __SPU_H__
+#define __SPU_H__
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <xine/video_out.h>
+#include <xine/video_overlay.h>
+#ifdef HAVE_DVDNAV
+# ifdef HAVE_DVDNAV_NAVTYPES_H
+# include <dvdnav/nav_types.h>
+# else
+# include <dvdread/nav_types.h>
+# endif
+#else
+# include "nav_types.h"
+#endif
+
+#define NUM_SEQ_BUFFERS 50
+#define MAX_STREAMS 32
+
+typedef struct spudec_clut_struct {
+#ifdef WORDS_BIGENDIAN
+ uint8_t entry0 : 4;
+ uint8_t entry1 : 4;
+ uint8_t entry2 : 4;
+ uint8_t entry3 : 4;
+#else
+ uint8_t entry1 : 4;
+ uint8_t entry0 : 4;
+ uint8_t entry3 : 4;
+ uint8_t entry2 : 4;
+#endif
+} spudec_clut_t;
+
+typedef struct {
+ uint8_t *buf;
+ uint32_t ra_offs; /* reassembly offset */
+ uint32_t seq_len;
+ uint32_t buf_len;
+ uint32_t cmd_offs;
+ int64_t pts; /* Base PTS of this sequence */
+ int32_t finished; /* Has this control sequence been finished? */
+ uint32_t complete; /* Has this reassembly been finished? */
+ uint32_t broken; /* this SPU is broken and should be dropped */
+} spudec_seq_t;
+
+typedef struct {
+ uint8_t *cmd_ptr;
+
+ uint32_t field_offs[2];
+ int32_t b_top, o_top;
+ int32_t b_bottom, o_bottom;
+ int32_t b_left, o_left;
+ int32_t b_right, o_right;
+
+ int32_t modified; /* Was the sub-picture modified? */
+ int32_t visible; /* Must the sub-picture be shown? */
+ int32_t forced_display; /* This overlay is a menu */
+ int32_t delay; /* Delay in 90Khz / 1000 */
+ int32_t need_clut; /* doesn't have the right clut yet */
+ int32_t cur_colors[4];/* current 4 colors been used */
+ int32_t vobsub; /* vobsub must be aligned to bottom */
+
+ uint32_t clut[16];
+} spudec_state_t;
+
+typedef struct spudec_stream_state_s {
+ spudec_seq_t ra_seq;
+ spudec_state_t state;
+ int64_t vpts;
+ int64_t pts;
+ int32_t overlay_handle;
+} spudec_stream_state_t;
+
+typedef struct {
+ spu_decoder_class_t decoder_class;
+} spudec_class_t;
+
+typedef struct pci_node_s pci_node_t;
+struct pci_node_s {
+ pci_t pci;
+ uint64_t vpts;
+ pci_node_t *next;
+};
+
+typedef struct spudec_decoder_s {
+ spu_decoder_t spu_decoder;
+
+ spudec_class_t *class;
+ xine_stream_t *stream;
+ spudec_stream_state_t spudec_stream_state[MAX_STREAMS];
+
+ video_overlay_event_t event;
+ video_overlay_object_t object;
+ int32_t menu_handle;
+
+ spudec_state_t state;
+
+ vo_overlay_t overlay;
+ int ovl_caps;
+ int output_open;
+ pthread_mutex_t nav_pci_lock;
+ pci_node_t pci_cur;
+ uint32_t buttonN; /* Current button number for highlights */
+ int32_t button_filter; /* Allow highlight changes or not */
+ int64_t last_event_vpts;
+} spudec_decoder_t;
+
+void spudec_reassembly (xine_t *xine, spudec_seq_t *seq, uint8_t *pkt_data, u_int pkt_len);
+void spudec_process( spudec_decoder_t *this, int stream_id);
+/* the nav functions must be called with the nav_pci_lock held */
+void spudec_decode_nav( spudec_decoder_t *this, buf_element_t *buf);
+void spudec_clear_nav_list(spudec_decoder_t *this);
+void spudec_update_nav(spudec_decoder_t *this);
+void spudec_process_nav(spudec_decoder_t *this);
+int spudec_copy_nav_to_overlay(xine_t *xine, pci_t* nav_pci, uint32_t* clut, int32_t button, int32_t mode,
+ vo_overlay_t * overlay, vo_overlay_t * base );
+
+#endif
diff --git a/src/spu_dec/spudvb_decoder.c b/src/spu_dec/spudvb_decoder.c
new file mode 100644
index 000000000..518903e01
--- /dev/null
+++ b/src/spu_dec/spudvb_decoder.c
@@ -0,0 +1,1218 @@
+/*
+ * Copyright (C) 2010 the xine project
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * DVB Subtitle decoder (ETS 300 743)
+ * (c) 2004 Mike Lampard <mlampard@users.sourceforge.net>
+ * based on the application dvbsub by Dave Chapman
+ *
+ * TODO:
+ * - Implement support for teletext based subtitles
+ */
+
+#include <pthread.h>
+#include <errno.h>
+
+/*#define LOG*/
+#define LOG_MODULE "spudvb"
+
+#include <xine/xine_internal.h>
+#include <xine/spu.h>
+#include <xine/osd.h>
+
+#define MAX_REGIONS 7
+
+#define SPU_MAX_WIDTH 1920
+#define SPU_MAX_HEIGHT 1080
+
+typedef struct {
+ int x, y;
+ unsigned char is_visible;
+} visible_region_t;
+
+typedef struct {
+ int page_time_out;
+ int page_version_number;
+ int page_state;
+ int page_id;
+ visible_region_t regions[MAX_REGIONS];
+} page_t;
+
+typedef struct {
+ int version_number;
+ int width, height;
+ int empty;
+ int depth;
+ int CLUT_id;
+ int objects_start;
+ int objects_end;
+ unsigned int object_pos[65536];
+ unsigned char *img;
+ osd_object_t *osd;
+} region_t;
+
+typedef struct {
+/* dvbsub stuff */
+ int x;
+ int y;
+ unsigned int curr_obj;
+ unsigned int curr_reg[64];
+ uint8_t *buf;
+ int i;
+ int i_bits;
+ int in_scanline;
+ int compat_depth;
+ page_t page;
+ region_t regions[MAX_REGIONS];
+ clut_t colours[MAX_REGIONS*256];
+ unsigned char trans[MAX_REGIONS*256];
+ struct {
+ unsigned char lut24[4], lut28[4], lut48[16];
+ } lut[MAX_REGIONS];
+} dvbsub_func_t;
+
+typedef struct dvb_spu_class_s {
+ spu_decoder_class_t class;
+ xine_t *xine;
+
+ int ignore_pts;
+} dvb_spu_class_t;
+
+typedef struct dvb_spu_decoder_s {
+ spu_decoder_t spu_decoder;
+
+ dvb_spu_class_t *class;
+ xine_stream_t *stream;
+
+ spu_dvb_descriptor_t *spu_descriptor;
+
+ /* dvbsub_osd_mutex should be locked around all calls to this->osd_renderer->show()
+ and this->osd_renderer->hide() */
+ pthread_mutex_t dvbsub_osd_mutex;
+
+ char *pes_pkt;
+ char *pes_pkt_wrptr;
+ unsigned int pes_pkt_size;
+
+ int64_t vpts;
+ int64_t end_vpts;
+
+ pthread_t dvbsub_timer_thread;
+ struct timespec dvbsub_hide_timeout;
+ pthread_cond_t dvbsub_restart_timeout;
+ dvbsub_func_t *dvbsub;
+ int show;
+} dvb_spu_decoder_t;
+
+static clut_t default_clut[256];
+static unsigned char default_trans[256];
+static int default_colours_init = 0;
+
+static void reset_clut (dvbsub_func_t *dvbsub)
+{
+ int i, r;
+
+ /* Reset the colour LUTs */
+ for (r = 0; r < MAX_REGIONS; ++r)
+ {
+ memcpy (dvbsub->colours + r * 256, default_clut, sizeof (default_clut));
+ memcpy (dvbsub->trans + r * 256, default_trans, sizeof (default_trans));
+ }
+
+ /* Reset the colour index LUTs */
+ for (r = 0; r < MAX_REGIONS; ++r)
+ {
+ dvbsub->lut[r].lut24[0] = 0x0;
+ dvbsub->lut[r].lut24[1] = 0x7;
+ dvbsub->lut[r].lut24[2] = 0x8;
+ dvbsub->lut[r].lut24[3] = 0xF;
+ dvbsub->lut[r].lut28[0] = 0x00;
+ dvbsub->lut[r].lut28[1] = 0x77;
+ dvbsub->lut[r].lut28[2] = 0x88;
+ dvbsub->lut[r].lut28[3] = 0xFF;
+ for (i = 0; i < 16; ++i)
+ dvbsub->lut[r].lut48[i] = i | i << 4;
+ }
+}
+
+static void update_osd(dvb_spu_decoder_t *this, int region_id)
+{
+ dvbsub_func_t *dvbsub = this->dvbsub;
+ region_t *reg = &dvbsub->regions[region_id];
+
+ if ( !reg->img ) {
+ if ( reg->osd ) {
+ pthread_mutex_lock( &this->dvbsub_osd_mutex );
+ this->stream->osd_renderer->free_object( reg->osd );
+ reg->osd = NULL;
+ pthread_mutex_unlock( &this->dvbsub_osd_mutex );
+ }
+ return;
+ }
+
+ if ( reg->osd ) {
+ if ( reg->width!=reg->osd->width || reg->height!=reg->osd->height ) {
+ pthread_mutex_lock( &this->dvbsub_osd_mutex );
+ this->stream->osd_renderer->free_object( reg->osd );
+ reg->osd = NULL;
+ pthread_mutex_unlock( &this->dvbsub_osd_mutex );
+ }
+ }
+
+ if ( !reg->osd )
+ reg->osd = this->stream->osd_renderer->new_object( this->stream->osd_renderer, reg->width, reg->height );
+}
+
+static void update_region (dvb_spu_decoder_t * this, int region_id, int region_width, int region_height, int fill, int fill_color)
+{
+
+ dvbsub_func_t *dvbsub = this->dvbsub;
+ region_t *reg = &dvbsub->regions[region_id];
+
+ /* reject invalid sizes and set some limits ! */
+ if ( region_width<=0 || region_height<=0 || region_width>SPU_MAX_WIDTH || region_height>SPU_MAX_HEIGHT ) {
+ if ( reg->img ) {
+ free( reg->img );
+ reg->img = NULL;
+ }
+ lprintf("rejected region %d = %dx%d\n", region_id, region_width, region_height );
+ return;
+ }
+
+ if ( reg->width*reg->height<region_width*region_height ) {
+ lprintf("update size of region %d = %dx%d\n", region_id, region_width, region_height);
+ if ( reg->img ) {
+ free( reg->img );
+ reg->img = NULL;
+ }
+ }
+
+ if ( !reg->img ) {
+ if ( !(reg->img=xine_xmalloc(region_width*region_height)) ) {
+ lprintf( "can't allocate mem for region %d\n", region_id );
+ return;
+ }
+ fill = 1;
+ }
+
+ if ( fill ) {
+ memset( reg->img, fill_color, region_width*region_height );
+ reg->empty = 1;
+ lprintf("FILL REGION %d\n", region_id);
+ }
+ reg->width = region_width;
+ reg->height = region_height;
+}
+
+
+static void do_plot (dvb_spu_decoder_t * this, int r, int x, int y, unsigned char pixel)
+{
+ int i;
+ dvbsub_func_t *dvbsub = this->dvbsub;
+
+ i = (y * dvbsub->regions[r].width) + x;
+ /* do some clipping */
+ if ( i<(dvbsub->regions[r].width*dvbsub->regions[r].height) ) {
+ dvbsub->regions[r].img[i] = pixel;
+ dvbsub->regions[r].empty = 0;
+ }
+}
+
+static void plot (dvb_spu_decoder_t * this, int r, int run_length, unsigned char pixel)
+{
+
+ dvbsub_func_t *dvbsub = this->dvbsub;
+
+ int x2 = dvbsub->x + run_length;
+
+ while (dvbsub->x < x2) {
+ do_plot (this, r, dvbsub->x, dvbsub->y, pixel);
+ dvbsub->x++;
+ }
+}
+
+static const uint8_t *lookup_lut (const dvbsub_func_t *dvbsub, int r)
+{
+ static const uint8_t identity_lut[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+
+ switch (dvbsub->compat_depth)
+ {
+ case 012: return dvbsub->lut[r].lut24;
+ case 013: return dvbsub->lut[r].lut28;
+ case 023: return dvbsub->lut[r].lut48;
+ default: return identity_lut;
+ }
+}
+
+static unsigned char next_datum (dvb_spu_decoder_t * this, int width)
+{
+ dvbsub_func_t *dvbsub = this->dvbsub;
+ unsigned char x = 0;
+
+ if (!dvbsub->i_bits)
+ dvbsub->i_bits = 8;
+
+ if (dvbsub->i_bits < width)
+ {
+ /* need to read from more than one byte; split it up */
+ width -= dvbsub->i_bits;
+ x = dvbsub->buf[dvbsub->i++] & ((1 << dvbsub->i_bits) - 1);
+ dvbsub->i_bits = 8;
+ return x << width | next_datum (this, width);
+ }
+
+ dvbsub->i_bits = (dvbsub->i_bits - width) & 7;
+ x = (dvbsub->buf[dvbsub->i] >> dvbsub->i_bits) & ((1 << width) - 1);
+
+ if (!dvbsub->i_bits)
+ ++dvbsub->i;
+
+ return x;
+}
+
+static void decode_2bit_pixel_code_string (dvb_spu_decoder_t * this, int r, int object_id, int ofs, int n)
+{
+ dvbsub_func_t *dvbsub = this->dvbsub;
+ int j;
+ const uint8_t *lut = lookup_lut (dvbsub, r);
+
+ if (dvbsub->in_scanline == 0)
+ dvbsub->in_scanline = 1;
+
+ dvbsub->i_bits = 0;
+ j = dvbsub->i + n;
+
+ while (dvbsub->i < j)
+ {
+ int next_bits = next_datum (this, 2);
+ int run_length;
+
+ if (next_bits)
+ {
+ /* single pixel */
+ plot (this, r, 1, lut[next_bits]);
+ continue;
+ }
+
+ /* switch 1 */
+ if (next_datum (this, 1) == 0)
+ {
+ /* run length, 3 to 10 pixels, colour given */
+ run_length = next_datum (this, 3);
+ plot (this, r, run_length + 3, lut[next_datum (this, 2)]);
+ continue;
+ }
+
+ /* switch 2 */
+ if (next_datum (this, 1) == 1)
+ {
+ /* single pixel, colour 0 */
+ plot (this, r, 1, lut[0]);
+ continue;
+ }
+
+ /* switch 3 */
+ switch (next_datum (this, 2))
+ {
+ case 0: /* end-of-string */
+ j = dvbsub->i; /* set the while cause FALSE */
+ break;
+ case 1: /* two pixels, colour 0 */
+ plot (this, r, 2, lut[0]);
+ break;
+ case 2: /* run length, 12 to 27 pixels (4-bit), colour given */
+ run_length = next_datum (this, 4);
+ plot (this, r, run_length + 12, lut[next_datum (this, 2)]);
+ break;
+ case 3: /* run length, 29 to 284 pixels (8-bit), colour given */
+ run_length = next_datum (this, 8);
+ plot (this, r, run_length + 29, lut[next_datum (this, 2)]);
+ }
+ }
+
+ if (dvbsub->i_bits) {
+ dvbsub->i++;
+ dvbsub->i_bits = 0;
+ }
+}
+
+static void decode_4bit_pixel_code_string (dvb_spu_decoder_t * this, int r, int object_id, int ofs, int n)
+{
+ dvbsub_func_t *dvbsub = this->dvbsub;
+ int j;
+ const uint8_t *lut = lookup_lut (dvbsub, r);
+
+ if (dvbsub->in_scanline == 0)
+ dvbsub->in_scanline = 1;
+
+ dvbsub->i_bits = 0;
+ j = dvbsub->i + n;
+
+ while (dvbsub->i < j)
+ {
+ int next_bits = next_datum (this, 4);
+ int run_length;
+
+ if (next_bits)
+ {
+ /* single pixel */
+ plot (this, r, 1, lut[next_bits]);
+ continue;
+ }
+
+ /* switch 1 */
+ if (next_datum (this, 1) == 0)
+ {
+ run_length = next_datum (this, 3);
+ if (!run_length)
+ /* end-of-string */
+ break;
+
+ /* run length, 3 to 9 pixels, colour 0 */
+ plot (this, r, run_length + 2, lut[0]);
+ continue;
+ }
+
+ /* switch 2 */
+ if (next_datum (this, 1) == 0)
+ {
+ /* run length, 4 to 7 pixels, colour given */
+ run_length = next_datum (this, 2);
+ plot (this, r, run_length + 4, lut[next_datum (this, 4)]);
+ continue;
+ }
+
+ /* switch 3 */
+ switch (next_datum (this, 2))
+ {
+ case 0: /* single pixel, colour 0 */
+ plot (this, r, 1, lut[0]);
+ break;
+ case 1: /* two pixels, colour 0 */
+ plot (this, r, 2, lut[0]);
+ break;
+ case 2: /* run length, 9 to 24 pixels (4-bit), colour given */
+ run_length = next_datum (this, 4);
+ plot (this, r, run_length + 9, lut[next_datum (this, 4)]);
+ break;
+ case 3: /* run length, 25 to 280 pixels (8-bit), colour given */
+ run_length = next_datum (this, 8);
+ plot (this, r, run_length + 25, lut[next_datum (this, 4)]);
+ }
+ }
+
+ if (dvbsub->i_bits) {
+ dvbsub->i++;
+ dvbsub->i_bits = 0;
+ }
+}
+
+static void decode_8bit_pixel_code_string (dvb_spu_decoder_t * this, int r, int object_id, int ofs, int n)
+{
+ dvbsub_func_t *dvbsub = this->dvbsub;
+ int j;
+
+ if (dvbsub->in_scanline == 0)
+ dvbsub->in_scanline = 1;
+
+ j = dvbsub->i + n;
+
+ while (dvbsub->i < j)
+ {
+ int next_bits = dvbsub->buf[dvbsub->i++];
+ int run_length;
+
+ if (next_bits)
+ {
+ /* single pixel */
+ plot (this, r, 1, next_bits);
+ continue;
+ }
+
+ /* switch 1 */
+ run_length = dvbsub->buf[dvbsub->i] & 127;
+
+ if (dvbsub->buf[dvbsub->i++] & 128)
+ {
+ /* run length, 3 to 127 pixels, colour given */
+ if (run_length > 2)
+ plot (this, r, run_length + 4, dvbsub->buf[dvbsub->i++]);
+ continue;
+ }
+
+ if (!run_length)
+ /* end-of-string */
+ break;
+
+ /* run length, 1 to 127 pixels, colour 0 */
+ plot (this, r, run_length + 2, 0);
+ }
+}
+
+static void recalculate_trans (dvb_spu_decoder_t *this)
+{
+ dvbsub_func_t *const dvbsub = this->dvbsub;
+ xine_spu_opacity_t opacity;
+ int i;
+
+ _x_spu_get_opacity (this->stream->xine, &opacity);
+ for (i = 0; i < MAX_REGIONS * 256; ++i) {
+ /* ETSI-300-743 says "full transparency if Y == 0". */
+ if (dvbsub->colours[i].y == 0)
+ dvbsub->trans[i] = 0;
+ else {
+ int v = _x_spu_calculate_opacity (&dvbsub->colours[i], dvbsub->colours[i].foo, &opacity);
+ dvbsub->trans[i] = v * 14 / 255 + 1;
+ }
+ }
+
+}
+
+static void set_clut(dvb_spu_decoder_t *this,int CLUT_id,int CLUT_entry_id,int Y_value, int Cr_value, int Cb_value, int T_value) {
+
+ dvbsub_func_t *dvbsub = this->dvbsub;
+
+ if ((CLUT_id>=MAX_REGIONS) || (CLUT_entry_id>255)) {
+ return;
+ }
+
+ dvbsub->colours[(CLUT_id*256)+CLUT_entry_id].y=Y_value;
+ dvbsub->colours[(CLUT_id*256)+CLUT_entry_id].cr=Cr_value;
+ dvbsub->colours[(CLUT_id*256)+CLUT_entry_id].cb=Cb_value;
+ dvbsub->colours[(CLUT_id*256)+CLUT_entry_id].foo = T_value;
+}
+
+static void process_CLUT_definition_segment(dvb_spu_decoder_t *this) {
+ int page_id,
+ segment_length,
+ CLUT_id,
+ CLUT_version_number;
+
+ int CLUT_entry_id,
+ CLUT_flag_8_bit,
+ CLUT_flag_4_bit,
+ CLUT_flag_2_bit,
+ full_range_flag,
+ Y_value,
+ Cr_value,
+ Cb_value,
+ T_value;
+ dvbsub_func_t *dvbsub = this->dvbsub;
+
+ int j;
+
+ page_id=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2;
+ segment_length=(dvbsub->buf[dvbsub->i]<<8)|dvbsub->buf[dvbsub->i+1]; dvbsub->i+=2;
+ j=dvbsub->i+segment_length;
+
+ CLUT_id=dvbsub->buf[dvbsub->i++];
+ CLUT_version_number=(dvbsub->buf[dvbsub->i]&0xf0)>>4;
+ dvbsub->i++;
+
+ while (dvbsub->i < j) {
+ CLUT_entry_id=dvbsub->buf[dvbsub->i++];
+
+ CLUT_flag_2_bit=(dvbsub->buf[dvbsub->i]&0x80)>>7;
+ CLUT_flag_4_bit=(dvbsub->buf[dvbsub->i]&0x40)>>6;
+ CLUT_flag_8_bit=(dvbsub->buf[dvbsub->i]&0x20)>>5;
+ full_range_flag=dvbsub->buf[dvbsub->i]&1;
+ dvbsub->i++;
+
+ if (full_range_flag==1) {
+ Y_value=dvbsub->buf[dvbsub->i++];
+ Cr_value=dvbsub->buf[dvbsub->i++];
+ Cb_value=dvbsub->buf[dvbsub->i++];
+ T_value=dvbsub->buf[dvbsub->i++];
+ } else {
+ Y_value = dvbsub->buf[dvbsub->i] & 0xfc;
+ Cr_value = (dvbsub->buf[dvbsub->i] << 6 | dvbsub->buf[dvbsub->i + 1] >> 2) & 0xf0;
+ Cb_value = (dvbsub->buf[dvbsub->i + 1] << 2) & 0xf0;
+ T_value = (dvbsub->buf[dvbsub->i + 1] & 3) * 0x55; /* expand only this one to full range! */
+ dvbsub->i+=2;
+ }
+ set_clut(this, CLUT_id,CLUT_entry_id,Y_value,Cr_value,Cb_value,T_value);
+ }
+}
+
+static void process_pixel_data_sub_block (dvb_spu_decoder_t * this, int r, int o, int ofs, int n)
+{
+ int data_type;
+ int j;
+
+ dvbsub_func_t *dvbsub = this->dvbsub;
+
+ j = dvbsub->i + n;
+
+ dvbsub->x = (dvbsub->regions[r].object_pos[o]) >> 16;
+ dvbsub->y = ((dvbsub->regions[r].object_pos[o]) & 0xffff) + ofs;
+ while (dvbsub->i < j) {
+ data_type = dvbsub->buf[dvbsub->i++];
+
+ switch (data_type) {
+ case 0:
+ dvbsub->i++;
+ case 0x10:
+ decode_2bit_pixel_code_string (this, r, o, ofs, n - 1);
+ break;
+ case 0x11:
+ decode_4bit_pixel_code_string (this, r, o, ofs, n - 1);
+ break;
+ case 0x12:
+ decode_8bit_pixel_code_string (this, r, o, ofs, n - 1);
+ break;
+ case 0x20: /* 2-to-4bit colour index map */
+ /* should this be implemented since we have an 8-bit overlay? */
+ dvbsub->lut[r].lut24[0] = dvbsub->buf[dvbsub->i ] >> 4;
+ dvbsub->lut[r].lut24[1] = dvbsub->buf[dvbsub->i ] & 0x0f;
+ dvbsub->lut[r].lut24[2] = dvbsub->buf[dvbsub->i + 1] >> 4;
+ dvbsub->lut[r].lut24[3] = dvbsub->buf[dvbsub->i + 1] & 0x0f;
+ dvbsub->i += 2;
+ break;
+ case 0x21: /* 2-to-8bit colour index map */
+ memcpy (dvbsub->lut[r].lut28, dvbsub->buf + dvbsub->i, 4);
+ dvbsub->i += 4;
+ break;
+ case 0x22:
+ memcpy (dvbsub->lut[r].lut48, dvbsub->buf + dvbsub->i, 16);
+ dvbsub->i += 16;
+ break;
+ case 0xf0:
+ dvbsub->in_scanline = 0;
+ dvbsub->x = (dvbsub->regions[r].object_pos[o]) >> 16;
+ dvbsub->y += 2;
+ break;
+ default:
+ lprintf ("unimplemented data_type %02x in pixel_data_sub_block\n", data_type);
+ }
+ }
+
+ dvbsub->i = j;
+}
+
+static void process_page_composition_segment (dvb_spu_decoder_t * this)
+{
+ int segment_length;
+ int region_id, region_x, region_y;
+ dvbsub_func_t *dvbsub = this->dvbsub;
+
+ dvbsub->page.page_id = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ segment_length = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+
+ int j = dvbsub->i + segment_length;
+
+ dvbsub->page.page_time_out = dvbsub->buf[dvbsub->i++];
+ if ( dvbsub->page.page_time_out>6 ) /* some timeout are insane, e.g. 65s ! */
+ dvbsub->page.page_time_out = 6;
+
+ int version = (dvbsub->buf[dvbsub->i] & 0xf0) >> 4;
+ if ( version == dvbsub->page.page_version_number )
+ return;
+ dvbsub->page.page_version_number = version;
+ dvbsub->page.page_state = (dvbsub->buf[dvbsub->i] & 0x0c) >> 2;
+ dvbsub->i++;
+
+ int r;
+ for (r=0; r<MAX_REGIONS; r++) { /* reset */
+ dvbsub->page.regions[r].is_visible = 0;
+ }
+
+ while (dvbsub->i < j) {
+ region_id = dvbsub->buf[dvbsub->i++];
+ dvbsub->i++; /* reserved */
+ region_x = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ region_y = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+
+ dvbsub->page.regions[region_id].x = region_x;
+ dvbsub->page.regions[region_id].y = region_y;
+ dvbsub->page.regions[region_id].is_visible = 1;
+ }
+}
+
+
+static void process_region_composition_segment (dvb_spu_decoder_t * this)
+{
+ int segment_length,
+ region_id,
+ region_version_number,
+ region_fill_flag, region_width, region_height, region_level_of_compatibility, region_depth, CLUT_id, region_8_bit_pixel_code, region_4_bit_pixel_code, region_2_bit_pixel_code;
+ int object_id, object_type, object_provider_flag, object_x, object_y, foreground_pixel_code, background_pixel_code;
+ int j;
+ int o;
+ dvbsub_func_t *dvbsub = this->dvbsub;
+
+ dvbsub->page.page_id = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ segment_length = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ j = dvbsub->i + segment_length;
+
+ region_id = dvbsub->buf[dvbsub->i++];
+ region_version_number = (dvbsub->buf[dvbsub->i] & 0xf0) >> 4;
+ region_fill_flag = (dvbsub->buf[dvbsub->i] & 0x08) >> 3;
+ dvbsub->i++;
+ region_width = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ region_height = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ region_level_of_compatibility = (dvbsub->buf[dvbsub->i] & 0xe0) >> 5;
+ region_depth = (dvbsub->buf[dvbsub->i] & 0x1c) >> 2;
+ dvbsub->compat_depth = region_level_of_compatibility << 3 | region_depth;
+ dvbsub->i++;
+ CLUT_id = dvbsub->buf[dvbsub->i++];
+ region_8_bit_pixel_code = dvbsub->buf[dvbsub->i++];
+ region_4_bit_pixel_code = (dvbsub->buf[dvbsub->i] & 0xf0) >> 4;
+ region_2_bit_pixel_code = (dvbsub->buf[dvbsub->i] & 0x0c) >> 2;
+ dvbsub->i++;
+
+ if(region_id>=MAX_REGIONS)
+ return;
+
+ if ( dvbsub->regions[region_id].version_number == region_version_number )
+ return;
+
+ dvbsub->regions[region_id].version_number = region_version_number;
+
+ /* Check if region size has changed and fill background. */
+ update_region (this, region_id, region_width, region_height, region_fill_flag, region_4_bit_pixel_code);
+ if ( CLUT_id<MAX_REGIONS )
+ dvbsub->regions[region_id].CLUT_id = CLUT_id;
+
+ dvbsub->regions[region_id].objects_start = dvbsub->i;
+ dvbsub->regions[region_id].objects_end = j;
+
+ for (o = 0; o < 65536; o++) {
+ dvbsub->regions[region_id].object_pos[o] = 0xffffffff;
+ }
+
+ while (dvbsub->i < j) {
+ object_id = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ object_type = (dvbsub->buf[dvbsub->i] & 0xc0) >> 6;
+ object_provider_flag = (dvbsub->buf[dvbsub->i] & 0x30) >> 4;
+ object_x = ((dvbsub->buf[dvbsub->i] & 0x0f) << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ object_y = ((dvbsub->buf[dvbsub->i] & 0x0f) << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+
+ dvbsub->regions[region_id].object_pos[object_id] = (object_x << 16) | object_y;
+
+ if ((object_type == 0x01) || (object_type == 0x02)) {
+ foreground_pixel_code = dvbsub->buf[dvbsub->i++];
+ background_pixel_code = dvbsub->buf[dvbsub->i++];
+ }
+ }
+
+}
+
+static void process_object_data_segment (dvb_spu_decoder_t * this)
+{
+ int segment_length, object_id, object_version_number, object_coding_method, non_modifying_colour_flag;
+
+ int top_field_data_block_length, bottom_field_data_block_length;
+
+ dvbsub_func_t *dvbsub = this->dvbsub;
+
+ int old_i;
+ int r;
+
+ dvbsub->page.page_id = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ segment_length = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+
+ object_id = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ dvbsub->curr_obj = object_id;
+ object_version_number = (dvbsub->buf[dvbsub->i] & 0xf0) >> 4;
+ object_coding_method = (dvbsub->buf[dvbsub->i] & 0x0c) >> 2;
+ non_modifying_colour_flag = (dvbsub->buf[dvbsub->i] & 0x02) >> 1;
+ dvbsub->i++;
+
+ old_i = dvbsub->i;
+ for (r = 0; r < MAX_REGIONS; r++) {
+
+ /* If this object is in this region... */
+ if (dvbsub->regions[r].img) {
+ if (dvbsub->regions[r].object_pos[object_id] != 0xffffffff) {
+ dvbsub->i = old_i;
+ if (object_coding_method == 0) {
+ top_field_data_block_length = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+ bottom_field_data_block_length = (dvbsub->buf[dvbsub->i] << 8) | dvbsub->buf[dvbsub->i + 1];
+ dvbsub->i += 2;
+
+ process_pixel_data_sub_block (this, r, object_id, 0, top_field_data_block_length);
+
+ if (bottom_field_data_block_length == 0)
+ {
+ /* handle bottom field == top field */
+ bottom_field_data_block_length = top_field_data_block_length;
+ dvbsub->i = old_i + 4;
+ }
+
+ process_pixel_data_sub_block (this, r, object_id, 1, bottom_field_data_block_length);
+ }
+ }
+ }
+ }
+}
+
+static void process_display_definition_segment(dvb_spu_decoder_t *this)
+{
+ /* FIXME: not implemented. */
+}
+
+static void unlock_mutex_cancellation_func(void *mutex_gen)
+{
+ pthread_mutex_t *mutex = (pthread_mutex_t*) mutex_gen;
+ pthread_mutex_unlock(mutex);
+}
+
+/* Thread routine that checks for subtitle timeout periodically.
+ To avoid unexpected subtitle hiding, calls to this->stream->osd_renderer->show()
+ should be in blocks like:
+
+ pthread_mutex_lock(&this->dvbsub_osd_mutex);
+ this->stream->osd_renderer->show(...);
+ this->dvbsub_hide_timeout.tv_sec = time(NULL) + timeout value;
+ pthread_cond_signal(&this->dvbsub_restart_timeout);
+ pthread_mutex_unlock(&this->dvbsub_osd_mutex);
+
+ This ensures that the timeout is changed with the lock held, and
+ that the thread is signalled to pick up the new timeout.
+*/
+static void* dvbsub_timer_func(void *this_gen)
+{
+ dvb_spu_decoder_t *this = (dvb_spu_decoder_t *) this_gen;
+ pthread_mutex_lock(&this->dvbsub_osd_mutex);
+ int i;
+
+ /* If we're cancelled via pthread_cancel, unlock the mutex */
+ pthread_cleanup_push(unlock_mutex_cancellation_func, &this->dvbsub_osd_mutex);
+
+ while(1)
+ {
+ /* Record the current timeout, and wait - note that pthread_cond_timedwait
+ will unlock the mutex on entry, and lock it on exit */
+ struct timespec timeout = this->dvbsub_hide_timeout;
+ int result = pthread_cond_timedwait(&this->dvbsub_restart_timeout,
+ &this->dvbsub_osd_mutex,
+ &this->dvbsub_hide_timeout);
+ if(result == ETIMEDOUT &&
+ timeout.tv_sec == this->dvbsub_hide_timeout.tv_sec &&
+ timeout.tv_nsec == this->dvbsub_hide_timeout.tv_nsec)
+ {
+ /* We timed out, and no-one changed the timeout underneath us.
+ Hide the OSD, then wait until we're signalled. */
+ if(this && this->stream && this->stream->osd_renderer)
+ {
+ for ( i=0; i<MAX_REGIONS; i++ ) {
+ if ( this->dvbsub->regions[i].osd ) {
+ this->stream->osd_renderer->hide( this->dvbsub->regions[i].osd, 0 );
+ lprintf("thread hiding = %d\n",i);
+ }
+ }
+ }
+ pthread_cond_wait(&this->dvbsub_restart_timeout, &this->dvbsub_osd_mutex);
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ return NULL;
+}
+
+static void downscale_region_image( region_t *reg, unsigned char *dest, int dest_width )
+{
+ float i, k, inc=reg->width/(float)dest_width;
+ int j;
+ for ( j=0; j<reg->height; j++ ) {
+ for ( i=0,k=0; i<reg->width && k<dest_width; i+=inc,k++ ) {
+ dest[(j*dest_width)+(int)k] = reg->img[(j*reg->width)+(int)i];
+ }
+ }
+}
+
+static void draw_subtitles (dvb_spu_decoder_t * this)
+{
+ int r;
+ int display=0;
+ int64_t dum;
+ int dest_width=0, dest_height, reg_width;
+ this->stream->video_out->status(this->stream->video_out, NULL, &dest_width, &dest_height, &dum);
+ unsigned char tmp[dest_width*576];
+ unsigned char *reg;
+
+ if ( !dest_width || !dest_height )
+ return;
+
+ /* render all regions onto the page */
+
+ for ( r=0; r<MAX_REGIONS; r++ ) {
+ if ( this->dvbsub->page.regions[r].is_visible )
+ display++;
+ }
+ if ( !display )
+ return;
+
+ for (r = 0; r < MAX_REGIONS; r++) {
+ if (this->dvbsub->regions[r].img) {
+ if (this->dvbsub->page.regions[r].is_visible && !this->dvbsub->regions[r].empty) {
+ update_osd( this, r );
+ if ( !this->dvbsub->regions[r].osd )
+ continue;
+ /* clear osd */
+ this->stream->osd_renderer->clear( this->dvbsub->regions[r].osd );
+ if ( this->dvbsub->regions[r].width>dest_width && !(this->stream->video_driver->get_capabilities(this->stream->video_driver) & VO_CAP_CUSTOM_EXTENT_OVERLAY)) {
+ downscale_region_image(&this->dvbsub->regions[r], tmp, dest_width);
+ reg = tmp;
+ reg_width = dest_width;
+ }
+ else {
+ reg = this->dvbsub->regions[r].img;
+ reg_width = this->dvbsub->regions[r].width;
+ }
+ this->stream->osd_renderer->set_palette( this->dvbsub->regions[r].osd, (uint32_t*)(&this->dvbsub->colours[this->dvbsub->regions[r].CLUT_id*256]), &this->dvbsub->trans[this->dvbsub->regions[r].CLUT_id*256]);
+ this->stream->osd_renderer->draw_bitmap( this->dvbsub->regions[r].osd, reg, 0, 0, reg_width, this->dvbsub->regions[r].height, NULL );
+ }
+ }
+ }
+
+ pthread_mutex_lock(&this->dvbsub_osd_mutex);
+ lprintf("this->vpts=%"PRId64"\n",this->vpts);
+ for ( r=0; r<MAX_REGIONS; r++ ) {
+ lprintf("region=%d, visible=%d, osd=%d, empty=%d\n", r, this->dvbsub->page.regions[r].is_visible, this->dvbsub->regions[r].osd?1:0, this->dvbsub->regions[r].empty );
+ if ( this->dvbsub->page.regions[r].is_visible && this->dvbsub->regions[r].osd && !this->dvbsub->regions[r].empty ) {
+ this->stream->osd_renderer->set_position( this->dvbsub->regions[r].osd, this->dvbsub->page.regions[r].x, this->dvbsub->page.regions[r].y );
+ this->stream->osd_renderer->show( this->dvbsub->regions[r].osd, this->vpts );
+ lprintf("show region = %d\n",r);
+ }
+ else {
+ if ( this->dvbsub->regions[r].osd ) {
+ this->stream->osd_renderer->hide( this->dvbsub->regions[r].osd, this->vpts );
+ lprintf("hide region = %d\n",r);
+ }
+ }
+ }
+ this->dvbsub_hide_timeout.tv_nsec = 0;
+ this->dvbsub_hide_timeout.tv_sec = time(NULL) + this->dvbsub->page.page_time_out;
+ lprintf("page_time_out %d\n",this->dvbsub->page.page_time_out);
+ pthread_cond_signal(&this->dvbsub_restart_timeout);
+ pthread_mutex_unlock(&this->dvbsub_osd_mutex);
+}
+
+static void spudec_decode_data (spu_decoder_t * this_gen, buf_element_t * buf)
+{
+ dvb_spu_decoder_t *this = (dvb_spu_decoder_t *) this_gen;
+ int new_i;
+ int data_identifier, subtitle_stream_id;
+ int segment_length, segment_type;
+ int PES_packet_length;
+ int i;
+
+ if((buf->type & 0xffff0000)!=BUF_SPU_DVB)
+ return;
+
+ if (buf->decoder_flags & BUF_FLAG_SPECIAL) {
+ if (buf->decoder_info[1] == BUF_SPECIAL_SPU_DVB_DESCRIPTOR) {
+ if (buf->decoder_info[2] == 0) {
+ /* Hide the osd - note that if the timeout thread times out, it'll rehide, which is harmless */
+ pthread_mutex_lock(&this->dvbsub_osd_mutex);
+ for ( i=0; i<MAX_REGIONS; i++ ) {
+ if ( this->dvbsub->regions[i].osd )
+ this->stream->osd_renderer->hide( this->dvbsub->regions[i].osd, 0 );
+ }
+ pthread_mutex_unlock(&this->dvbsub_osd_mutex);
+ }
+ else {
+ xine_fast_memcpy (this->spu_descriptor, buf->decoder_info_ptr[2], buf->decoder_info[2]);
+ }
+ }
+ return;
+ }
+
+ /* accumulate data */
+ if (buf->decoder_info[2]) {
+ memset (this->pes_pkt, 0xff, 64*1024);
+ this->pes_pkt_wrptr = this->pes_pkt;
+ this->pes_pkt_size = buf->decoder_info[2];
+
+ xine_fast_memcpy (this->pes_pkt, buf->content, buf->size);
+ this->pes_pkt_wrptr += buf->size;
+
+ this->vpts = 0;
+ }
+ else {
+ if (this->pes_pkt && (this->pes_pkt_wrptr != this->pes_pkt)) {
+ xine_fast_memcpy (this->pes_pkt_wrptr, buf->content, buf->size);
+ this->pes_pkt_wrptr += buf->size;
+ }
+ }
+
+ /* don't ask metronom for a vpts but rather do the calculation
+ * because buf->pts could be too far in future and metronom won't accept
+ * further backwards pts (see metronom_got_spu_packet) */
+ if (!this->class->ignore_pts && buf->pts > 0) {
+ metronom_t *metronom = this->stream->metronom;
+ int64_t vpts_offset = metronom->get_option( metronom, METRONOM_VPTS_OFFSET );
+ int64_t spu_offset = metronom->get_option( metronom, METRONOM_SPU_OFFSET );
+ int64_t vpts = (int64_t)(buf->pts)+vpts_offset+spu_offset;
+ metronom_clock_t *clock = this->stream->xine->clock;
+ int64_t curvpts = clock->get_current_time( clock );
+ /* if buf->pts is unreliable, show page asap (better than nothing) */
+ lprintf("spu_vpts=%"PRId64" - current_vpts=%"PRId64"\n", vpts, curvpts);
+ if ( vpts<=curvpts || (vpts-curvpts)>(5*90000) )
+ this->vpts = 0;
+ else
+ this->vpts = vpts;
+ }
+
+ /* completely ignore pts since it makes a lot of problems with various providers */
+ /* this->vpts = 0; */
+
+ /* process the pes section */
+
+ PES_packet_length = this->pes_pkt_size;
+
+ this->dvbsub->buf = this->pes_pkt;
+
+ this->dvbsub->i = 0;
+
+ data_identifier = this->dvbsub->buf[this->dvbsub->i++];
+ subtitle_stream_id = this->dvbsub->buf[this->dvbsub->i++];
+
+ while (this->dvbsub->i <= (PES_packet_length)) {
+ /* SUBTITLING SEGMENT */
+ this->dvbsub->i++;
+ segment_type = this->dvbsub->buf[this->dvbsub->i++];
+
+ this->dvbsub->page.page_id = (this->dvbsub->buf[this->dvbsub->i] << 8) | this->dvbsub->buf[this->dvbsub->i + 1];
+ segment_length = (this->dvbsub->buf[this->dvbsub->i + 2] << 8) | this->dvbsub->buf[this->dvbsub->i + 3];
+ new_i = this->dvbsub->i + segment_length + 4;
+
+ /* only process complete segments */
+ if(new_i > (this->pes_pkt_wrptr - this->pes_pkt))
+ break;
+ /* verify we've the right segment */
+ if(this->dvbsub->page.page_id==this->spu_descriptor->comp_page_id){
+ /* SEGMENT_DATA_FIELD */
+ switch (segment_type & 0xff) {
+ case 0x10:
+ process_page_composition_segment (this);
+ break;
+ case 0x11:
+ process_region_composition_segment (this);
+ break;
+ case 0x12:
+ process_CLUT_definition_segment(this);
+ break;
+ case 0x13:
+ process_object_data_segment (this);
+ break;
+ case 0x14:
+ process_display_definition_segment(this);
+ break;
+ case 0x80: /* Page is now completely rendered */
+ recalculate_trans(this);
+ draw_subtitles( this );
+ break;
+ case 0xFF: /* stuffing */
+ break;
+ default:
+ return;
+ break;
+ }
+ }
+ this->dvbsub->i = new_i;
+ }
+}
+
+static void spudec_reset (spu_decoder_t * this_gen)
+{
+ dvb_spu_decoder_t *this = (dvb_spu_decoder_t *) this_gen;
+ int i;
+
+ /* Hide the osd - if the timeout thread times out, it'll rehide harmlessly */
+ pthread_mutex_lock(&this->dvbsub_osd_mutex);
+ for ( i=0; i<MAX_REGIONS; i++ ) {
+ if ( this->dvbsub->regions[i].osd )
+ this->stream->osd_renderer->hide(this->dvbsub->regions[i].osd, 0);
+ this->dvbsub->regions[i].version_number = -1;
+ }
+ this->dvbsub->page.page_version_number = -1;
+ reset_clut (this->dvbsub);
+
+ pthread_mutex_unlock(&this->dvbsub_osd_mutex);
+}
+
+static void spudec_discontinuity (spu_decoder_t * this_gen)
+{
+ /* do nothing */
+}
+
+static void spudec_dispose (spu_decoder_t * this_gen)
+{
+ dvb_spu_decoder_t *this = (dvb_spu_decoder_t *) this_gen;
+ int i;
+
+ pthread_cancel(this->dvbsub_timer_thread);
+ pthread_join(this->dvbsub_timer_thread, NULL);
+ pthread_mutex_destroy(&this->dvbsub_osd_mutex);
+ pthread_cond_destroy(&this->dvbsub_restart_timeout);
+
+ if(this->spu_descriptor){
+ free(this->spu_descriptor);
+ this->spu_descriptor=NULL;
+ }
+
+ for ( i=0; i<MAX_REGIONS; i++ ) {
+ if ( this->dvbsub->regions[i].img )
+ free( this->dvbsub->regions[i].img );
+ if ( this->dvbsub->regions[i].osd )
+ this->stream->osd_renderer->free_object( this->dvbsub->regions[i].osd );
+ }
+
+ if (this->pes_pkt)
+ free (this->pes_pkt);
+
+ if (this->dvbsub)
+ free (this->dvbsub);
+
+ free (this);
+}
+
+static spu_decoder_t *dvb_spu_class_open_plugin (spu_decoder_class_t * class_gen, xine_stream_t * stream)
+{
+ dvb_spu_decoder_t *this = calloc(1, sizeof (dvb_spu_decoder_t));
+ dvb_spu_class_t *class = (dvb_spu_class_t *)class_gen;
+
+#define YUVA(r, g, b, a) (clut_t) { COMPUTE_V(r, g, b), COMPUTE_U(r, g, b), COMPUTE_V(r, g, b), a }
+#define GETBIT(s, v1, v2, tr) \
+ r = s + ((i & 1) ? v1 : 0) + ((i & 0x10) ? v2 : 0); \
+ g = s + ((i & 2) ? v1 : 0) + ((i & 0x20) ? v2 : 0); \
+ b = s + ((i & 4) ? v1 : 0) + ((i & 0x40) ? v2 : 0); \
+ a = tr
+
+ if (!default_colours_init)
+ {
+ int i;
+ default_clut[0] = YUVA(0, 0, 0, 0);
+ for (i = 1; i < 256; i++) {
+ uint8_t r, g, b, a;
+ if (i < 8) {
+ GETBIT(0, 255, 0, 63);
+ } else switch (i & 0x88) {
+ case 0x00: GETBIT( 0, 85, 170, 255); break;
+ case 0x08: GETBIT( 0, 85, 170, 127); break;
+ case 0x80: GETBIT(127, 43, 85, 255); break;
+ default : GETBIT( 0, 43, 85, 255); break;
+ }
+ default_trans[i] = a;
+ default_clut[i] = YUVA(r, g, b, a);
+ }
+ default_colours_init = 1;
+ }
+
+
+ this->spu_decoder.decode_data = spudec_decode_data;
+ this->spu_decoder.reset = spudec_reset;
+ this->spu_decoder.discontinuity = spudec_discontinuity;
+ this->spu_decoder.dispose = spudec_dispose;
+ this->spu_decoder.get_interact_info = NULL;
+ this->spu_decoder.set_button = NULL;
+
+ this->class = class;
+ this->stream = stream;
+
+ this->pes_pkt = calloc(65, 1024);
+ this->spu_descriptor = calloc(1, sizeof(spu_dvb_descriptor_t));
+
+ this->dvbsub = calloc(1, sizeof (dvbsub_func_t));
+
+ int i;
+ for (i = 0; i < MAX_REGIONS; i++) {
+ this->dvbsub->page.regions[i].is_visible = 0;
+ this->dvbsub->regions[i].img = NULL;
+ this->dvbsub->regions[i].osd = NULL;
+ this->dvbsub->regions[i].CLUT_id = 0;
+ }
+
+ {
+ xine_spu_opacity_t opacity;
+ static const clut_t black = { 0, 0, 0, 0 };
+ int t;
+
+ _x_spu_get_opacity (this->stream->xine, &opacity);
+ t = _x_spu_calculate_opacity (&black, 0, &opacity);
+
+ for (i = 0; i < MAX_REGIONS * 256; i++)
+ this->dvbsub->colours[i].foo = t;
+ }
+
+ pthread_mutex_init(&this->dvbsub_osd_mutex, NULL);
+ pthread_cond_init(&this->dvbsub_restart_timeout, NULL);
+ this->dvbsub_hide_timeout.tv_nsec = 0;
+ this->dvbsub_hide_timeout.tv_sec = time(NULL);
+ pthread_create(&this->dvbsub_timer_thread, NULL, dvbsub_timer_func, this);
+
+ return (spu_decoder_t *) this;
+}
+
+static void dvb_spu_decoder_class_dispose (spu_decoder_class_t * this_gen)
+{
+ dvb_spu_class_t *this = (dvb_spu_class_t *) this_gen;
+
+ this->xine->config->unregister_callback(this->xine->config, "subtitles.dvb.ignore_pts");
+
+ free (this);
+}
+
+static void *init_spu_decoder_plugin (xine_t * xine, void *data)
+{
+
+ dvb_spu_class_t *this;
+ this = calloc(1, sizeof (dvb_spu_class_t));
+
+ this->class.open_plugin = dvb_spu_class_open_plugin;
+ this->class.identifier = "spudvb";
+ this->class.description = N_("DVB subtitle decoder plugin");
+ this->class.dispose = dvb_spu_decoder_class_dispose;
+
+ this->xine = xine;
+
+ return &this->class;
+}
+
+
+/* plugin catalog information */
+static const uint32_t supported_types[] = { BUF_SPU_DVB, 0 };
+
+static const decoder_info_t spudec_info = {
+ supported_types, /* supported types */
+ 1 /* priority */
+};
+
+const plugin_info_t xine_plugin_info[] EXPORTED = {
+/* type, API, "name", version, special_info, init_function */
+ {PLUGIN_SPU_DECODER, 17, "spudvb", XINE_VERSION_CODE, &spudec_info,
+ &init_spu_decoder_plugin},
+ {PLUGIN_NONE, 0, "", 0, NULL, NULL}
+};
diff --git a/src/spu_dec/spuhdmv_decoder.c b/src/spu_dec/spuhdmv_decoder.c
new file mode 100644
index 000000000..85d35aec5
--- /dev/null
+++ b/src/spu_dec/spuhdmv_decoder.c
@@ -0,0 +1,1072 @@
+/*
+ * Copyright (C) 2000-2009 the xine project
+ *
+ * Copyright (C) 2009 Petri Hintukainen <phintuka@users.sourceforge.net>
+ *
+ * This file is part of xine, a unix video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * Decoder for HDMV/BluRay bitmap subtitles
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <xine/xine_internal.h>
+#include <xine/buffer.h>
+#include <xine/xineutils.h>
+#include <xine/video_out.h>
+#include <xine/video_overlay.h>
+
+#define XINE_HDMV_TRACE(x...) printf(x)
+/*#define XINE_HDMV_TRACE(x...) */
+#define XINE_HDMV_ERROR(x...) fprintf(stderr, "spuhdmv: " x)
+/*#define XINE_HDMV_ERROR(x...) lprintf(x) */
+
+#ifndef MAX
+# define MAX(a,b) (a>b)?(a):(b)
+#endif
+
+enum {
+ SEGTYPE_PALETTE = 0x14,
+ SEGTYPE_OBJECT = 0x15,
+ SEGTYPE_PRESENTATION_SEGMENT = 0x16,
+ SEGTYPE_WINDOW_DEFINITION = 0x17,
+ SEGTYPE_INTERACTIVE = 0x18,
+ SEGTYPE_END_OF_DISPLAY = 0x80,
+} eSegmentType;
+
+/*
+ * cached palette (xine-lib format)
+ */
+typedef struct subtitle_clut_s subtitle_clut_t;
+struct subtitle_clut_s {
+ uint8_t id;
+ uint32_t color[256];
+ uint8_t trans[256];
+ subtitle_clut_t *next;
+
+ int shown;
+};
+
+/*
+ * cached RLE image (xine-lib format)
+ */
+typedef struct subtitle_object_s subtitle_object_t;
+struct subtitle_object_s {
+ uint16_t id;
+ uint16_t xpos, ypos;
+ uint16_t width, height;
+
+ /* xine format */
+ rle_elem_t *rle;
+ unsigned int num_rle;
+ size_t data_size;
+
+ /* HDMV format (used when object does not fit to single segment) */
+ uint32_t data_len; /* size of complete object */
+ uint8_t *raw_data; /* partial RLE data in HDMV format */
+ size_t raw_data_len; /* bytes buffered */
+ size_t raw_data_size; /* allocated size */
+
+ subtitle_object_t *next;
+
+ int shown;
+};
+
+/*
+ * Window definition
+ */
+typedef struct window_def_s window_def_t;
+struct window_def_s {
+ uint8_t id;
+ uint16_t xpos, ypos;
+ uint16_t width, height;
+
+ window_def_t *next;
+
+ int shown;
+};
+
+
+/*
+ * decoded SPU
+ */
+typedef struct composition_object_s composition_object_t;
+struct composition_object_s {
+ uint8_t window_id_ref;
+ uint16_t object_id_ref;
+
+ uint16_t xpos, ypos;
+
+ uint8_t forced_flag;
+ uint8_t cropped_flag;
+ uint16_t crop_horiz_pos, crop_vert_pos;
+ uint16_t crop_width, crop_height;
+
+ composition_object_t *next;
+
+ int shown;
+};
+
+typedef struct composition_descriptor_s composition_descriptor_t;
+struct composition_descriptor_s {
+ uint16_t number;
+ uint8_t state;
+};
+
+typedef struct presentation_segment_s presentation_segment_t;
+struct presentation_segment_s {
+ composition_descriptor_t comp_descr;
+
+ uint8_t palette_update_flag;
+ uint8_t palette_id_ref;
+ uint8_t object_number;
+
+ composition_object_t *comp_objs;
+
+ presentation_segment_t *next;
+
+ int64_t pts;
+ int shown;
+};
+
+/*
+ * list handling
+ */
+
+#define LIST_REPLACE(list, obj, FREE_FUNC) \
+ do { \
+ unsigned int id = obj->id; \
+ \
+ /* insert to list */ \
+ obj->next = list; \
+ list = obj; \
+ \
+ /* remove old */ \
+ while (obj->next && obj->next->id != id) \
+ obj = obj->next; \
+ if (obj->next) { \
+ void *tmp = (void*)obj->next; \
+ obj->next = obj->next->next; \
+ FREE_FUNC(tmp); \
+ } \
+ } while (0);
+
+#define LIST_DESTROY(list, FREE_FUNC) \
+ while (list) { \
+ void *tmp = (void*)list; \
+ list = list->next; \
+ FREE_FUNC(tmp); \
+ }
+
+static void free_subtitle_object(void *ptr)
+{
+ if (ptr) {
+ free(((subtitle_object_t*)ptr)->rle);
+ free(((subtitle_object_t*)ptr)->raw_data);
+ free(ptr);
+ }
+}
+static void free_presentation_segment(void *ptr)
+{
+ if (ptr) {
+ presentation_segment_t *seg = (presentation_segment_t*)ptr;
+ LIST_DESTROY(seg->comp_objs, free);
+ free(ptr);
+ }
+}
+
+
+/*
+ * segment_buffer_t
+ *
+ * assemble and decode segments
+ */
+
+typedef struct {
+ /* current segment */
+ int segment_len; /* length of current segment (without 3-byte header) */
+ uint8_t segment_type; /* current segment type */
+ uint8_t *segment_data; /* pointer to current segment payload */
+ uint8_t *segment_end; /* pointer to last byte + 1 of current segment */
+ uint8_t error; /* boolean: buffer overflow etc. */
+
+ /* accumulated data */
+ uint8_t *buf; /* */
+ size_t len; /* count of unprocessed bytes */
+ size_t data_size; /* allocated buffer size */
+} segment_buffer_t;
+
+/*
+ * mgmt
+ */
+
+static segment_buffer_t *segbuf_init(void)
+{
+ segment_buffer_t *buf = calloc(1, sizeof(segment_buffer_t));
+ return buf;
+}
+
+static void segbuf_dispose(segment_buffer_t *buf)
+{
+ if (buf->buf)
+ free (buf->buf);
+ free (buf);
+}
+
+static void segbuf_reset(segment_buffer_t *buf)
+{
+ buf->segment_end = buf->segment_data = buf->buf;
+ buf->len = 0;
+ buf->segment_len = -1;
+ buf->segment_type = 0;
+ buf->error = 0;
+}
+
+/*
+ * assemble, parse
+ */
+
+static void segbuf_parse_segment_header(segment_buffer_t *buf)
+{
+ if (buf->len > 2) {
+ buf->segment_type = buf->buf[0];
+ buf->segment_len = (buf->buf[1] << 8) | buf->buf[2];
+ buf->segment_data = buf->buf + 3;
+ buf->segment_end = buf->segment_data + buf->segment_len;
+ buf->error = 0;
+
+ if ( buf->segment_type < 0x14 ||
+ ( buf->segment_type > 0x18 &&
+ buf->segment_type != 0x80)) {
+ XINE_HDMV_ERROR("unknown segment type 0x%02x, resetting\n", buf->segment_type);
+ segbuf_reset(buf);
+ }
+ } else {
+ buf->segment_len = -1;
+ buf->error = 1;
+ }
+}
+
+static void segbuf_fill(segment_buffer_t *buf, uint8_t *data, size_t len)
+{
+ if (buf->len + len > buf->data_size) {
+ buf->data_size = buf->len + len;
+ if (buf->buf)
+ buf->buf = realloc(buf->buf, buf->data_size);
+ else
+ buf->buf = malloc(buf->data_size);
+ }
+
+ memcpy(buf->buf + buf->len, data, len);
+ buf->len += len;
+
+ segbuf_parse_segment_header(buf);
+}
+
+static int segbuf_segment_complete(segment_buffer_t *buf)
+{
+ return (buf->segment_len >= 0) && (buf->len >= (unsigned)buf->segment_len + 3);
+}
+
+static void segbuf_skip_segment(segment_buffer_t *buf)
+{
+ if (segbuf_segment_complete (buf)) {
+ buf->len -= buf->segment_len + 3;
+ if (buf->len > 0)
+ memmove(buf->buf, buf->buf + buf->segment_len + 3, buf->len);
+
+ segbuf_parse_segment_header(buf);
+
+ XINE_HDMV_TRACE(" skip_segment: %zd bytes left\n", buf->len);
+ } else {
+ XINE_HDMV_ERROR(" skip_segment: ERROR - %zd bytes queued, %d required\n",
+ buf->len, buf->segment_len);
+ segbuf_reset (buf);
+ }
+}
+
+/*
+ * access segment data
+ */
+
+static uint8_t segbuf_segment_type(segment_buffer_t *buf)
+{
+ return buf->segment_type;
+}
+
+static size_t segbuf_data_length(segment_buffer_t *buf)
+{
+ ssize_t val = buf->segment_end - buf->segment_data;
+ if (val < 0) val = 0;
+ return (size_t)val;
+}
+
+static uint8_t segbuf_get_u8(segment_buffer_t *buf)
+{
+ if (!(buf->error = ++buf->segment_data > buf->segment_end))
+ return buf->segment_data[-1];
+ XINE_HDMV_ERROR("segbuf_get_u8: read failed (end of segment reached) !\n");
+ return 0;
+}
+
+static uint16_t segbuf_get_u16(segment_buffer_t *buf)
+{
+ return (segbuf_get_u8(buf) << 8) | segbuf_get_u8(buf);
+}
+
+static uint32_t segbuf_get_u24(segment_buffer_t *buf)
+{
+ return (segbuf_get_u8(buf) << 16) | (segbuf_get_u8(buf) << 8) | segbuf_get_u8(buf);
+}
+
+/*
+ * decode segments
+ */
+
+static subtitle_clut_t *segbuf_decode_palette(segment_buffer_t *buf)
+{
+ uint8_t palette_id = segbuf_get_u8 (buf);
+ uint8_t palette_version_number = segbuf_get_u8 (buf);
+
+ size_t len = segbuf_data_length(buf);
+ size_t entries = len / 5;
+ size_t i;
+
+ if (buf->error)
+ return NULL;
+
+ if (len % 5) {
+ XINE_HDMV_ERROR(" decode_palette: segment size error (%zd ; expected %zd for %zd entries)\n",
+ len, (5 * entries), entries);
+ return NULL;
+ }
+ XINE_HDMV_TRACE("decode_palette: %zd items (id %d, version %d)\n",
+ entries, palette_id, palette_version_number);
+
+ /* convert to xine-lib clut */
+ subtitle_clut_t *clut = calloc(1, sizeof(subtitle_clut_t));
+ clut->id = palette_id;
+
+ for (i = 0; i < entries; i++) {
+ uint8_t index = segbuf_get_u8 (buf);
+ uint8_t Y = segbuf_get_u8 (buf);
+ uint8_t Cr = segbuf_get_u8 (buf);
+ uint8_t Cb = segbuf_get_u8 (buf);
+ uint8_t alpha = segbuf_get_u8 (buf);
+ clut->color[index] = (Y << 16) | (Cr << 8) | Cb;
+ clut->trans[index] = alpha >> 4;
+ }
+
+ return clut;
+}
+
+static int segbuf_decode_rle(segment_buffer_t *buf, subtitle_object_t *obj)
+{
+ int x = 0, y = 0;
+ int rle_size = sizeof(rle_elem_t) * obj->width / 16 * obj->height + 1;
+ rle_elem_t *rlep = malloc(rle_size);
+
+ free (obj->rle);
+ obj->rle = rlep;
+ obj->data_size = rle_size;
+ obj->num_rle = 0;
+
+ /* convert to xine-lib rle format */
+ while (y < obj->height && !buf->error) {
+
+ /* decode RLE element */
+ uint8_t byte = segbuf_get_u8 (buf);
+ if (byte != 0) {
+ rlep->color = byte;
+ rlep->len = 1;
+ } else {
+ byte = segbuf_get_u8 (buf);
+ if (!(byte & 0x80)) {
+ rlep->color = 0;
+ if (!(byte & 0x40))
+ rlep->len = byte & 0x3f;
+ else
+ rlep->len = ((byte & 0x3f) << 8) | segbuf_get_u8 (buf);
+ } else {
+ if (!(byte & 0x40))
+ rlep->len = byte & 0x3f;
+ else
+ rlep->len = ((byte & 0x3f) << 8) | segbuf_get_u8 (buf);
+ rlep->color = segbuf_get_u8 (buf);
+ }
+ }
+
+ /* move to next element */
+ if (rlep->len > 0) {
+ x += rlep->len;
+ rlep++;
+ obj->num_rle ++;
+ } else {
+ /* end of line marker (00 00) */
+ if (x < obj->width) {
+ rlep->len = obj->width - x;
+ rlep->color = 0xff;
+ rlep++;
+ obj->num_rle ++;
+ }
+ x = 0;
+ y++;
+ }
+
+ /* grow allocated RLE data size ? */
+ if (obj->data_size <= (obj->num_rle + 1) * sizeof(rle_elem_t)) {
+ obj->data_size *= 2;
+ obj->rle = realloc(obj->rle, obj->data_size);
+ rlep = obj->rle + obj->num_rle;
+ }
+ }
+
+ return buf->error;
+}
+
+static subtitle_object_t *segbuf_decode_object(segment_buffer_t *buf, subtitle_object_t *objects)
+{
+ uint16_t object_id = segbuf_get_u16(buf);
+ uint8_t version = segbuf_get_u8 (buf);
+ uint8_t seq_desc = segbuf_get_u8 (buf);
+
+ XINE_HDMV_TRACE(" decode_object: object_id %d, version %d, seq 0x%x\n",
+ object_id, version, seq_desc);
+
+ if (seq_desc & 0x80) {
+ /* new object (first-in-sequence flag set) */
+
+ subtitle_object_t *obj = calloc(1, sizeof(subtitle_object_t));
+
+ obj->id = object_id;
+ obj->data_len = segbuf_get_u24(buf);
+ obj->width = segbuf_get_u16(buf);
+ obj->height = segbuf_get_u16(buf);
+
+ if (buf->error) {
+ XINE_HDMV_TRACE(" decode error at object header\n");
+ free_subtitle_object(obj);
+ return NULL;
+ }
+
+ obj->data_len -= 4; /* width, height parsed */
+
+ XINE_HDMV_TRACE(" object length %d bytes, size %dx%d\n", obj->data_len, obj->width, obj->height);
+
+ if (obj->data_len > segbuf_data_length(buf)) {
+ XINE_HDMV_TRACE(" object length %d bytes, have only %zd bytes -> missing %d bytes\n",
+ obj->data_len, segbuf_data_length(buf), obj->data_len - (int)segbuf_data_length(buf));
+
+ if (obj->raw_data)
+ free(obj->raw_data);
+
+ /* store partial RLE data in HDMV format */
+ obj->raw_data_len = segbuf_data_length(buf);
+ obj->raw_data_size = MAX(obj->data_len, obj->raw_data_len);
+ obj->raw_data = malloc(obj->raw_data_size);
+ memcpy(obj->raw_data, buf->segment_data, obj->raw_data_len);
+
+ return obj;
+ }
+
+ segbuf_decode_rle (buf, obj);
+
+ if (buf->error) {
+ XINE_HDMV_TRACE(" decode error at RLE data\n");
+ free_subtitle_object(obj);
+ return NULL;
+ }
+
+ return obj;
+ }
+
+ /* not first-of-sequence --> append data to already existing objct */
+
+ /* search for object */
+ while (objects && objects->id != object_id)
+ objects = objects->next;
+
+ if (!objects) {
+ XINE_HDMV_TRACE(" object not found from list, discarding segment\n");
+ return NULL;
+ }
+
+ /* store partial RLE data in HDMV format */
+ if (objects->raw_data_size < objects->raw_data_len + segbuf_data_length(buf)) {
+ XINE_HDMV_ERROR("object larger than object size !\n");
+ return NULL;
+ }
+ memcpy(objects->raw_data + objects->raw_data_len, buf->segment_data, segbuf_data_length(buf));
+ objects->raw_data_len += segbuf_data_length(buf);
+
+ /* if complete, decode RLE data */
+ if (objects->raw_data_len >= objects->data_len) {
+ /* create dummy buffer for segbuf_decode_rle */
+ segment_buffer_t tmpbuf = {
+ .segment_data = objects->raw_data,
+ .segment_end = objects->raw_data + objects->raw_data_len,
+ };
+
+ /* decode RLE data */
+ segbuf_decode_rle (&tmpbuf, objects);
+
+ if (tmpbuf.error) {
+ XINE_HDMV_TRACE(" error decoding multi-segment object\n");
+ }
+
+ /* free decode buffer */
+ free(objects->raw_data);
+ objects->raw_data = NULL;
+ objects->raw_data_len = 0;
+ objects->raw_data_size = 0;
+ }
+
+ return NULL;
+}
+
+static window_def_t *segbuf_decode_window_definition(segment_buffer_t *buf)
+{
+ window_def_t *wnd = calloc(1, sizeof(window_def_t));
+
+ uint8_t a = segbuf_get_u8 (buf);
+ wnd->id = segbuf_get_u8 (buf);
+ wnd->xpos = segbuf_get_u16 (buf);
+ wnd->ypos = segbuf_get_u16 (buf);
+ wnd->width = segbuf_get_u16 (buf);
+ wnd->height = segbuf_get_u16 (buf);
+
+ XINE_HDMV_TRACE(" window: [%02x %d] %d,%d %dx%d\n", a,
+ wnd->id, wnd->xpos, wnd->ypos, wnd->width, wnd->height);
+
+ if (buf->error) {
+ free(wnd);
+ return NULL;
+ }
+
+ return wnd;
+}
+
+static int segbuf_decode_video_descriptor(segment_buffer_t *buf)
+{
+ uint16_t width = segbuf_get_u16(buf);
+ uint16_t height = segbuf_get_u16(buf);
+ uint8_t frame_rate = segbuf_get_u8 (buf);
+
+ XINE_HDMV_TRACE(" video_descriptor: %dx%d fps %d\n", width, height, frame_rate);
+
+ return buf->error;
+}
+
+static int segbuf_decode_composition_descriptor(segment_buffer_t *buf, composition_descriptor_t *descr)
+{
+ descr->number = segbuf_get_u16(buf);
+ descr->state = segbuf_get_u8 (buf) & 0xc0;
+
+ XINE_HDMV_TRACE(" composition_descriptor: number %d, state %d\n", descr->number, descr->state);
+ return buf->error;
+}
+
+static composition_object_t *segbuf_decode_composition_object(segment_buffer_t *buf)
+{
+ composition_object_t *cobj = calloc(1, sizeof(composition_object_t));
+
+ cobj->object_id_ref = segbuf_get_u16 (buf);
+ cobj->window_id_ref = segbuf_get_u8 (buf);
+ uint8_t tmp = segbuf_get_u8 (buf);
+ cobj->cropped_flag = !!(tmp & 0x80);
+ cobj->forced_flag = !!(tmp & 0x40);
+ cobj->xpos = segbuf_get_u16 (buf);
+ cobj->ypos = segbuf_get_u16 (buf);
+ if (cobj->cropped_flag) {
+ /* x,y where to take the image from */
+ cobj->crop_horiz_pos = segbuf_get_u8 (buf);
+ cobj->crop_vert_pos = segbuf_get_u8 (buf);
+ /* size of the cropped image */
+ cobj->crop_width = segbuf_get_u8 (buf);
+ cobj->crop_height = segbuf_get_u8 (buf);
+ }
+
+ if (buf->error) {
+ free(cobj);
+ return NULL;
+ }
+
+ XINE_HDMV_TRACE(" composition_object: id: %d, win: %d, position %d,%d crop %d forced %d\n",
+ cobj->object_id_ref, cobj->window_id_ref, cobj->xpos, cobj->ypos,
+ cobj->cropped_flag, cobj->forced_flag);
+
+ return cobj;
+}
+
+static presentation_segment_t *segbuf_decode_presentation_segment(segment_buffer_t *buf)
+{
+ presentation_segment_t *seg = calloc(1, sizeof(presentation_segment_t));
+ int index;
+
+ segbuf_decode_video_descriptor (buf);
+ segbuf_decode_composition_descriptor (buf, &seg->comp_descr);
+
+ seg->palette_update_flag = !!((segbuf_get_u8(buf)) & 0x80);
+ seg->palette_id_ref = segbuf_get_u8 (buf);
+ seg->object_number = segbuf_get_u8 (buf);
+
+ XINE_HDMV_TRACE(" presentation_segment: object_number %d, palette %d\n",
+ seg->object_number, seg->palette_id_ref);
+
+ for (index = 0; index < seg->object_number; index++) {
+ composition_object_t *cobj = segbuf_decode_composition_object (buf);
+ cobj->next = seg->comp_objs;
+ seg->comp_objs = cobj;
+ }
+
+ if (buf->error) {
+ free_presentation_segment(seg);
+ return NULL;
+ }
+
+ return seg;
+}
+
+static rle_elem_t *copy_crop_rle(subtitle_object_t *obj, composition_object_t *cobj)
+{
+ /* TODO: cropping (w,h sized image from pos x,y) */
+
+ rle_elem_t *rle = calloc (obj->num_rle, sizeof(rle_elem_t));
+ memcpy (rle, obj->rle, obj->num_rle * sizeof(rle_elem_t));
+ return rle;
+}
+
+
+/*
+ * xine plugin
+ */
+
+typedef struct {
+ spu_decoder_class_t decoder_class;
+} spuhdmv_class_t;
+
+typedef struct spuhdmv_decoder_s {
+ spu_decoder_t spu_decoder;
+
+ spuhdmv_class_t *class;
+ xine_stream_t *stream;
+
+ segment_buffer_t *buf;
+
+ subtitle_clut_t *cluts;
+ subtitle_object_t *objects;
+ window_def_t *windows;
+ presentation_segment_t *segments;
+
+ int overlay_handles[MAX_OBJECTS];
+
+ int64_t pts;
+
+} spuhdmv_decoder_t;
+
+static void free_objs(spuhdmv_decoder_t *this)
+{
+ LIST_DESTROY (this->cluts, free);
+ LIST_DESTROY (this->objects, free_subtitle_object);
+ LIST_DESTROY (this->windows, free);
+ LIST_DESTROY (this->segments, free_presentation_segment);
+}
+
+static int decode_palette(spuhdmv_decoder_t *this)
+{
+ /* decode */
+ subtitle_clut_t *clut = segbuf_decode_palette(this->buf);
+ if (!clut)
+ return 1;
+
+ LIST_REPLACE (this->cluts, clut, free);
+
+ return 0;
+}
+
+static int decode_object(spuhdmv_decoder_t *this)
+{
+ /* decode */
+ subtitle_object_t *obj = segbuf_decode_object(this->buf, this->objects);
+ if (!obj)
+ return 1;
+
+ LIST_REPLACE (this->objects, obj, free_subtitle_object);
+
+ return 0;
+}
+
+static int decode_window_definition(spuhdmv_decoder_t *this)
+{
+ /* decode */
+ window_def_t *wnd = segbuf_decode_window_definition (this->buf);
+ if (!wnd)
+ return 1;
+
+ LIST_REPLACE (this->windows, wnd, free);
+
+ return 0;
+}
+
+static int decode_presentation_segment(spuhdmv_decoder_t *this)
+{
+ /* decode */
+ presentation_segment_t *seg = segbuf_decode_presentation_segment(this->buf);
+ if (!seg)
+ return 1;
+
+ seg->pts = this->pts;
+
+ /* epoch start or acquistion point -> drop cached objects */
+ if (seg->comp_descr.state) {
+ free_objs(this);
+ }
+
+ /* replace */
+ if (this->segments)
+ LIST_DESTROY(this->segments, free_presentation_segment);
+ this->segments = seg;
+
+ return 0;
+}
+
+static int show_overlay(spuhdmv_decoder_t *this, composition_object_t *cobj, unsigned int palette_id_ref,
+ int overlay_index, int64_t pts, int force_update)
+{
+ video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out);
+ metronom_t *metronom = this->stream->metronom;
+ video_overlay_event_t event = {0};
+ vo_overlay_t overlay = {0};
+
+ /* find palette */
+ subtitle_clut_t *clut = this->cluts;
+ while (clut && clut->id != palette_id_ref)
+ clut = clut->next;
+ if (!clut) {
+ XINE_HDMV_TRACE(" show_overlay: clut %d not found !\n", palette_id_ref);
+ return -1;
+ }
+
+ /* find RLE image */
+ subtitle_object_t *obj = this->objects;
+ while (obj && obj->id != cobj->object_id_ref)
+ obj = obj->next;
+ if (!obj) {
+ XINE_HDMV_TRACE(" show_overlay: object %d not found !\n", cobj->object_id_ref);
+ return -1;
+ }
+ if (!obj->rle) {
+ XINE_HDMV_TRACE(" show_overlay: object %d RLE data not decoded !\n", cobj->object_id_ref);
+ return -1;
+ }
+
+ /* find window */
+ window_def_t *wnd = this->windows;
+ while (wnd && wnd->id != cobj->window_id_ref)
+ wnd = wnd->next;
+ if (!wnd) {
+ XINE_HDMV_TRACE(" show_overlay: window %d not found !\n", cobj->window_id_ref);
+ return -1;
+ }
+
+ /* do not show again if all elements are unchanged */
+ if (!force_update && clut->shown && obj->shown && wnd->shown && cobj->shown)
+ return 0;
+ clut->shown = obj->shown = wnd->shown = cobj->shown = 1;
+
+ /* copy palette to xine overlay */
+ overlay.rgb_clut = 0;
+ memcpy(overlay.color, clut->color, sizeof(uint32_t) * 256);
+ memcpy(overlay.trans, clut->trans, sizeof(uint8_t) * 256);
+
+ /* copy and crop RLE image to xine overlay */
+ overlay.width = obj->width;
+ overlay.height = obj->height;
+
+ overlay.rle = copy_crop_rle (obj, cobj);
+ overlay.num_rle = obj->num_rle;
+ overlay.data_size = obj->num_rle * sizeof(rle_elem_t);
+
+ /* */
+
+ overlay.x = /*wnd->xpos +*/ cobj->xpos;
+ overlay.y = /*wnd->ypos +*/ cobj->ypos;
+
+ overlay.unscaled = 0;
+ overlay.hili_top = -1;
+ overlay.hili_bottom = -1;
+ overlay.hili_left = -1;
+ overlay.hili_right = -1;
+
+ XINE_HDMV_TRACE(" -> overlay: %d,%d %dx%d\n",
+ overlay.x, overlay.y, overlay.width, overlay.height);
+
+
+ /* set timings */
+
+ if (pts > 0)
+ event.vpts = metronom->got_spu_packet (metronom, pts);
+ else
+ event.vpts = 0;
+
+
+ /* generate SHOW event */
+
+ this->stream->video_out->enable_ovl(this->stream->video_out, 1);
+
+ if (this->overlay_handles[overlay_index] < 0)
+ this->overlay_handles[overlay_index] = ovl_manager->get_handle(ovl_manager, 0);
+
+ event.event_type = OVERLAY_EVENT_SHOW;
+ event.object.handle = this->overlay_handles[overlay_index];
+ event.object.overlay = &overlay;
+ event.object.object_type = 0; /* subtitle */
+
+ ovl_manager->add_event (ovl_manager, (void *)&event);
+
+ return 0;
+}
+
+static void hide_overlays(spuhdmv_decoder_t *this, int64_t pts)
+{
+ video_overlay_event_t event = {0};
+ int i = 0;
+
+ while (this->overlay_handles[i] >= 0) {
+ XINE_HDMV_TRACE(" -> HIDE %d\n", i);
+
+ video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out);
+ metronom_t *metronom = this->stream->metronom;
+
+ event.object.handle = this->overlay_handles[i];
+ if (this)
+ event.vpts = metronom->got_spu_packet (metronom, pts);
+ else
+ event.vpts = 0;
+ event.event_type = OVERLAY_EVENT_HIDE;
+ event.object.overlay = NULL;
+ ovl_manager->add_event (ovl_manager, (void *)&event);
+
+ //this->overlay_handles[i] = -1;
+ i++;
+ }
+}
+
+static void update_overlays(spuhdmv_decoder_t *this)
+{
+ presentation_segment_t *pseg = this->segments;
+
+ while (pseg) {
+
+ if (!pseg->object_number) {
+
+ /* HIDE */
+ if (!pseg->shown)
+ hide_overlays (this, pseg->pts);
+
+ } else {
+
+ /* SHOW */
+ composition_object_t *cobj = pseg->comp_objs;
+ int i;
+
+ for (i = 0; i < pseg->object_number; i++) {
+ if (!cobj) {
+ XINE_HDMV_ERROR("show_overlays: composition object %d missing !\n", i);
+ } else {
+ show_overlay(this, cobj, pseg->palette_id_ref, i, pseg->pts, !pseg->shown);
+ cobj = cobj->next;
+ }
+ }
+ }
+
+ pseg->shown = 1;
+
+ pseg = pseg->next;
+ }
+}
+
+static void decode_segment(spuhdmv_decoder_t *this)
+{
+ XINE_HDMV_TRACE("*** new segment, pts %010"PRId64": 0x%02x (%8d bytes)\n",
+ this->pts, this->buf->segment_type, this->buf->segment_len);
+
+ switch (segbuf_segment_type(this->buf)) {
+ case SEGTYPE_PALETTE:
+ XINE_HDMV_TRACE(" segment: PALETTE\n");
+ decode_palette(this);
+ break;
+ case SEGTYPE_OBJECT:
+ XINE_HDMV_TRACE(" segment: OBJECT\n");
+ decode_object(this);
+ break;
+ case SEGTYPE_PRESENTATION_SEGMENT:
+ XINE_HDMV_TRACE(" segment: PRESENTATION SEGMENT\n");
+ decode_presentation_segment(this);
+ break;
+ case SEGTYPE_WINDOW_DEFINITION:
+ XINE_HDMV_TRACE(" segment: WINDOW DEFINITION\n");
+ decode_window_definition(this);
+ break;
+ case SEGTYPE_INTERACTIVE:
+ XINE_HDMV_TRACE(" segment: INTERACTIVE\n");
+ break;
+ case SEGTYPE_END_OF_DISPLAY:
+ XINE_HDMV_TRACE(" segment: END OF DISPLAY\n");
+#if 0
+ /* drop all cached objects */
+ free_objs(this);
+#endif
+ break;
+ default:
+ XINE_HDMV_ERROR(" segment type 0x%x unknown, skipping\n", segbuf_segment_type(this->buf));
+ break;
+ }
+ if (this->buf->error) {
+ XINE_HDMV_ERROR("*** DECODE ERROR ***\n");
+ }
+
+ update_overlays (this);
+}
+
+static void close_osd(spuhdmv_decoder_t *this)
+{
+ video_overlay_manager_t *ovl_manager = this->stream->video_out->get_overlay_manager (this->stream->video_out);
+
+ int i = 0;
+ while (this->overlay_handles[i] >= 0) {
+ ovl_manager->free_handle(ovl_manager, this->overlay_handles[i]);
+ this->overlay_handles[i] = -1;
+ i++;
+ }
+}
+
+static void spudec_decode_data (spu_decoder_t * this_gen, buf_element_t * buf)
+{
+ spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen;
+
+ if ((buf->type & 0xffff0000) != BUF_SPU_HDMV)
+ return;
+
+ if (buf->size < 1)
+ return;
+
+ if (buf->pts)
+ this->pts = buf->pts;
+
+#ifdef DUMP_SPU_DATA
+ int i;
+ for(i = 0; i < buf->size; i++)
+ printf(" %02x", buf->content[i]);
+ printf("\n");
+#endif
+
+ segbuf_fill(this->buf, buf->content, buf->size);
+
+ while (segbuf_segment_complete(this->buf)) {
+ decode_segment(this);
+ segbuf_skip_segment(this->buf);
+ }
+}
+
+static void spudec_reset (spu_decoder_t * this_gen)
+{
+ spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen;
+
+ if (this->buf)
+ segbuf_reset(this->buf);
+
+ free_objs(this);
+
+ close_osd(this);
+}
+
+static void spudec_discontinuity (spu_decoder_t *this_gen)
+{
+ spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen;
+
+ close_osd(this);
+}
+
+static void spudec_dispose (spu_decoder_t *this_gen)
+{
+ spuhdmv_decoder_t *this = (spuhdmv_decoder_t *) this_gen;
+
+ close_osd (this);
+ segbuf_dispose (this->buf);
+
+ free_objs(this);
+
+ free (this);
+}
+
+static spu_decoder_t *open_plugin (spu_decoder_class_t *class_gen, xine_stream_t *stream)
+{
+ spuhdmv_decoder_t *this;
+
+ this = (spuhdmv_decoder_t *) calloc(1, sizeof (spuhdmv_decoder_t));
+
+ this->spu_decoder.decode_data = spudec_decode_data;
+ this->spu_decoder.reset = spudec_reset;
+ this->spu_decoder.discontinuity = spudec_discontinuity;
+ this->spu_decoder.dispose = spudec_dispose;
+ this->spu_decoder.get_interact_info = NULL;
+ this->spu_decoder.set_button = NULL;
+ this->stream = stream;
+ this->class = (spuhdmv_class_t *) class_gen;
+
+ this->buf = segbuf_init();
+
+ memset(this->overlay_handles, 0xff, sizeof(this->overlay_handles)); /* --> -1 */
+
+ return &this->spu_decoder;
+}
+
+static void *init_plugin (xine_t *xine, void *data)
+{
+ spuhdmv_class_t *this;
+
+ this = calloc(1, sizeof (spuhdmv_class_t));
+
+ this->decoder_class.open_plugin = open_plugin;
+ this->decoder_class.identifier = "spuhdmv";
+ this->decoder_class.description = "HDMV/BluRay bitmap SPU decoder plugin";
+ this->decoder_class.dispose = default_spu_decoder_class_dispose;
+
+ return this;
+}
+
+/* plugin catalog information */
+static const uint32_t supported_types[] = { BUF_SPU_HDMV, 0 };
+
+static const decoder_info_t dec_info_data = {
+ supported_types, /* supported types */
+ 5 /* priority */
+};
+
+const plugin_info_t xine_plugin_info[] EXPORTED = {
+ /* type, API, "name", version, special_info, init_function */
+ { PLUGIN_SPU_DECODER, 17, "spuhdmv", XINE_VERSION_CODE, &dec_info_data, &init_plugin },
+ { PLUGIN_NONE, 0, "", 0, NULL, NULL }
+};
diff --git a/src/spu_dec/sputext_decoder.c b/src/spu_dec/sputext_decoder.c
new file mode 100644
index 000000000..0eb42d665
--- /dev/null
+++ b/src/spu_dec/sputext_decoder.c
@@ -0,0 +1,1211 @@
+/*
+ * Copyright (C) 2000-2004 the xine project
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#define LOG_MODULE "libsputext"
+#define LOG_VERBOSE
+/*
+#define LOG
+*/
+
+#include <xine/buffer.h>
+#include <xine/xine_internal.h>
+#include <xine/xineutils.h>
+#include <xine/osd.h>
+
+#define SUB_MAX_TEXT 5 /* lines */
+#define SUB_BUFSIZE 256 /* chars per line */
+
+/* alignment in SSA codes */
+#define ALIGN_LEFT 1
+#define ALIGN_CENTER 2
+#define ALIGN_RIGHT 3
+#define ALIGN_BOTTOM 0
+#define ALIGN_TOP 4
+#define ALIGN_MIDDLE 8
+#define GET_X_ALIGNMENT(a) ((a) & 3)
+#define GET_Y_ALIGNMENT(a) ((a) - ((a) & 3))
+
+/* subtitles projection */
+/* for subrip file with SSA tags, those values are always correct.*/
+/* But for SSA files, those values are the default ones. we have */
+/* to use PlayResX and PlayResY defined in [Script Info] section. */
+/* not implemented yet... */
+#define SPU_PROJECTION_X 384
+#define SPU_PROJECTION_Y 288
+
+
+
+#define rgb2yuv(R,G,B) ((((((66*R+129*G+25*B+128)>>8)+16)<<8)|(((112*R-94*G-18*B+128)>>8)+128))<<8|(((-38*R-74*G+112*B+128)>>8)+128))
+
+static const uint32_t sub_palette[22]={
+/* RED */
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(50,10,10),
+ rgb2yuv(120,20,20),
+ rgb2yuv(185,50,50),
+ rgb2yuv(255,70,70),
+/* BLUE */
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,0,0),
+ rgb2yuv(0,30,50),
+ rgb2yuv(0,90,120),
+ rgb2yuv(0,140,185),
+ rgb2yuv(0,170,255)
+};
+
+static const uint8_t sub_trans[22]={
+ 0, 0, 3, 6, 8, 10, 12, 14, 15, 15, 15,
+ 0, 0, 3, 6, 8, 10, 12, 14, 15, 15, 15
+};
+
+typedef enum {
+ SUBTITLE_SIZE_TINY = 0,
+ SUBTITLE_SIZE_SMALL,
+ SUBTITLE_SIZE_NORMAL,
+ SUBTITLE_SIZE_LARGE,
+ SUBTITLE_SIZE_VERY_LARGE,
+ SUBTITLE_SIZE_HUGE,
+
+ SUBTITLE_SIZE_NUM /* number of values in enum */
+} subtitle_size;
+
+#define FONTNAME_SIZE 100
+
+typedef struct sputext_class_s {
+ spu_decoder_class_t class;
+
+ subtitle_size subtitle_size; /* size of subtitles */
+ int vertical_offset;
+ char font[FONTNAME_SIZE]; /* subtitle font */
+#ifdef HAVE_FT2
+ char font_ft[FILENAME_MAX]; /* subtitle font */
+ int use_font_ft; /* use Freetype */
+#endif
+ const char *src_encoding; /* encoding of subtitle file */
+ int use_unscaled; /* use unscaled OSD if possible */
+
+ xine_t *xine;
+
+} sputext_class_t;
+
+
+/* Convert subtiles coordinates in window coordinates. */
+/* (a, b) --> (x + a * dx, y + b * dy) */
+typedef struct video2wnd_s {
+ int x;
+ int y;
+ double dx;
+ double dy;
+} video2wnd_t;
+
+typedef struct sputext_decoder_s {
+ spu_decoder_t spu_decoder;
+
+ sputext_class_t *class;
+ xine_stream_t *stream;
+
+ int ogm;
+ int lines;
+ char text[SUB_MAX_TEXT][SUB_BUFSIZE];
+
+ /* below 3 variables are the same from class. use to detect
+ * when something changes.
+ */
+ subtitle_size subtitle_size; /* size of subtitles */
+ int vertical_offset;
+ char font[FILENAME_MAX]; /* subtitle font */
+ char *buf_encoding; /* encoding of subtitle buffer */
+
+ int width; /* frame width */
+ int height; /* frame height */
+ int font_size;
+ int line_height;
+ int started;
+ int finished;
+
+ osd_renderer_t *renderer;
+ osd_object_t *osd;
+ int current_osd_text;
+ uint32_t spu_palette[OVL_PALETTE_SIZE];
+ uint8_t spu_trans[OVL_PALETTE_SIZE];
+
+ int64_t img_duration;
+ int64_t last_subtitle_end; /* no new subtitle before this vpts */
+ int unscaled; /* use unscaled OSD */
+
+ int last_y; /* location of the previous subtitle */
+ int last_lines; /* number of lines of the previous subtitle */
+ video2wnd_t video2wnd;
+} sputext_decoder_t;
+
+static inline char *get_font (sputext_class_t *class)
+{
+#ifdef HAVE_FT2
+ return class->use_font_ft ? class->font_ft : class->font;
+#else
+ return class->font;
+#endif
+}
+
+static void update_font_size (sputext_decoder_t *this, int force_update) {
+ static const int sizes[SUBTITLE_SIZE_NUM] = { 16, 20, 24, 32, 48, 64 };
+
+ if ((this->subtitle_size != this->class->subtitle_size) ||
+ (this->vertical_offset != this->class->vertical_offset) ||
+ force_update) {
+
+ this->subtitle_size = this->class->subtitle_size;
+ this->vertical_offset = this->class->vertical_offset;
+ this->last_lines = 0;
+
+ this->font_size = sizes[this->class->subtitle_size];
+
+ this->line_height = this->font_size + 10;
+
+ /* Create a full-window OSD */
+ if( this->osd )
+ this->renderer->free_object (this->osd);
+
+ this->osd = this->renderer->new_object (this->renderer,
+ this->width,
+ this->height);
+
+ this->renderer->set_font (this->osd, get_font (this->class), this->font_size);
+
+ this->renderer->set_position (this->osd, 0, 0);
+ }
+}
+
+static void update_output_size (sputext_decoder_t *this) {
+ const int unscaled = this->class->use_unscaled &&
+ (this->stream->video_out->get_capabilities(this->stream->video_out) &
+ VO_CAP_UNSCALED_OVERLAY);
+
+ if( unscaled != this->unscaled ) {
+ this->unscaled = unscaled;
+ this->width = 0; /* force update */
+ }
+
+ /* initialize decoder if needed */
+ if( this->unscaled ) {
+ if( this->width != this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_WIDTH) ||
+ this->height != this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_HEIGHT) ||
+ !this->img_duration || !this->osd ) {
+
+ int width = 0, height = 0;
+
+ this->stream->video_out->status(this->stream->video_out, NULL,
+ &width, &height, &this->img_duration );
+ if( width && height ) {
+
+ this->width = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_WIDTH);
+ this->height = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_HEIGHT);
+
+ if(!this->osd || (this->width && this->height)) {
+
+ /* in unscaled mode, we have to convert subtitle position in window coordinates. */
+ /* we have a scale factor because video may be zommed */
+ /* and a displacement factor because video may have blacks lines. */
+ int output_width, output_height, output_xoffset, output_yoffset;
+
+ output_width = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_OUTPUT_WIDTH);
+ output_height = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_OUTPUT_HEIGHT);
+ output_xoffset = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_OUTPUT_XOFFSET);
+ output_yoffset = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_OUTPUT_YOFFSET);
+
+ /* driver don't seen to be capable to give us those values */
+ /* fallback to a default full-window values */
+ if (output_width <= 0 || output_height <= 0) {
+ output_width = this->width;
+ output_height = this->height;
+ output_xoffset = 0;
+ output_yoffset = 0;
+ }
+
+ this->video2wnd.x = output_xoffset;
+ this->video2wnd.y = output_yoffset;
+ this->video2wnd.dx = (double)output_width / SPU_PROJECTION_X;
+ this->video2wnd.dy = (double)output_height / SPU_PROJECTION_Y;
+
+ this->renderer = this->stream->osd_renderer;
+ update_font_size (this, 1);
+ }
+ }
+ }
+ } else {
+ if( !this->width || !this->height || !this->img_duration || !this->osd ) {
+
+ this->width = 0;
+ this->height = 0;
+
+ this->stream->video_out->status(this->stream->video_out, NULL,
+ &this->width, &this->height, &this->img_duration );
+
+ if(!this->osd || ( this->width && this->height)) {
+ this->renderer = this->stream->osd_renderer;
+
+ /* in scaled mode, we have to convert subtitle position in film coordinates. */
+ this->video2wnd.x = 0;
+ this->video2wnd.y = 0;
+ this->video2wnd.dx = (double)this->width / SPU_PROJECTION_X;
+ this->video2wnd.dy = (double)this->height / SPU_PROJECTION_Y;
+
+ update_font_size (this, 1);
+ }
+ }
+ }
+}
+
+static int parse_utf8_size(const void *buf)
+{
+ const uint8_t *c = buf;
+ if ( c[0]<0x80 )
+ return 1;
+
+ if( c[1]==0 )
+ return 1;
+ if ( (c[0]>=0xC2 && c[0]<=0xDF) && (c[1]>=0x80 && c[1]<=0xBF) )
+ return 2;
+
+ if( c[2]==0 )
+ return 2;
+ else if ( c[0]==0xE0 && (c[1]>=0xA0 && c[1]<=0xBF) && (c[2]>=0x80 && c[1]<=0xBF) )
+ return 3;
+ else if ( (c[0]>=0xE1 && c[0]<=0xEC) && (c[1]>=0x80 && c[1]<=0xBF) && (c[2]>=0x80 && c[1]<=0xBF) )
+ return 3;
+ else if ( c[0]==0xED && (c[1]>=0x80 && c[1]<=0x9F) && (c[2]>=0x80 && c[1]<=0xBF) )
+ return 3;
+ else if ( c[0]==0xEF && (c[1]>=0xA4 && c[1]<=0xBF) && (c[2]>=0x80 && c[1]<=0xBF) )
+ return 3;
+ else
+ return 1;
+}
+
+static int ogm_render_line_internal(sputext_decoder_t *this, int x, int y, const char *text, int render)
+{
+ const size_t length = strlen (text);
+ size_t i = 0;
+
+ while (i <= length) {
+
+ if (text[i] == '<') {
+ if (!strncmp("<b>", text+i, 3)) {
+ /* enable Bold color */
+ if (render)
+ this->current_osd_text = OSD_TEXT2;
+ i=i+3;
+ continue;
+ } else if (!strncmp("</b>", text+i, 4)) {
+ /* disable BOLD */
+ if (render)
+ this->current_osd_text = OSD_TEXT1;
+ i=i+4;
+ continue;
+ } else if (!strncmp("<i>", text+i, 3)) {
+ /* enable italics color */
+ if (render)
+ this->current_osd_text = OSD_TEXT3;
+ i=i+3;
+ continue;
+ } else if (!strncmp("</i>", text+i, 4)) {
+ /* disable italics */
+ if (render)
+ this->current_osd_text = OSD_TEXT1;
+ i=i+4;
+ continue;
+ } else if (!strncmp("<font>", text+i, 6)) {
+ /*Do somethink to disable typing
+ fixme - no teststreams*/
+ i=i+6;
+ continue;
+ } else if (!strncmp("</font>", text+i, 7)) {
+ /*Do somethink to enable typing
+ fixme - no teststreams*/
+ i=i+7;
+ continue;
+ }
+ }
+ if (text[i] == '{') {
+
+ if (!strncmp("{\\", text+i, 2)) {
+ int value;
+
+ if (sscanf(text+i, "{\\b%d}", &value) == 1) {
+ if (render) {
+ if (value)
+ this->current_osd_text = OSD_TEXT2;
+ else
+ this->current_osd_text = OSD_TEXT1;
+ }
+ } else if (sscanf(text+i, "{\\i%d}", &value) == 1) {
+ if (render) {
+ if (value)
+ this->current_osd_text = OSD_TEXT3;
+ else
+ this->current_osd_text = OSD_TEXT1;
+ }
+ }
+ char *const end = strstr(text+i+2, "}");
+ if (end) {
+ i=end-text+1;
+ continue;
+ }
+ }
+ }
+
+ char letter[5];
+ const char *const encoding = this->buf_encoding ? : this->class->src_encoding;
+ const int isutf8 = !strcmp(encoding, "utf-8");
+ const size_t shift = isutf8 ? parse_utf8_size (&text[i]) : 1;
+ memcpy(letter,&text[i],shift);
+ letter[shift]=0;
+
+ if (render)
+ this->renderer->render_text(this->osd, x, y, letter, this->current_osd_text);
+
+ int w, dummy;
+ this->renderer->get_text_size(this->osd, letter, &w, &dummy);
+ x += w;
+ i += shift;
+ }
+
+ return x;
+}
+
+static inline int ogm_get_width(sputext_decoder_t *this, char* text) {
+ return ogm_render_line_internal (this, 0, 0, text, 0);
+}
+
+static inline void ogm_render_line(sputext_decoder_t *this, int x, int y, char* text) {
+ ogm_render_line_internal (this, x, y, text, 1);
+}
+
+/* read SSA tags at begening of text. Suported tags are : */
+/* \a : alignment in SSA code (see #defines) */
+/* \an : alignment in 'numpad code' */
+/* \pos : absolute position of subtitles. Alignment define origin. */
+static void read_ssa_tag(sputext_decoder_t *this, const char* text,
+ int* alignment, int* sub_x, int* sub_y, int* max_width) {
+
+ int in_tag = 0;
+
+ (*alignment) = 2;
+ (*sub_x) = -1;
+ (*sub_y) = -1;
+
+ while (*text) {
+
+ /* wait for tag begin, allow space and tab */
+ if (in_tag == 0) {
+ if (*text == '{') in_tag = 1;
+ else if ((*text != ' ') && (*text != '\t')) break;
+
+ /* parse SSA command */
+ } else {
+ if (*text == '\\') {
+ if (sscanf(text, "\\pos(%d,%d)", sub_x, sub_y) == 2) {
+ text += 8; /* just for speed up, 8 is the minimal with */
+ }
+
+ if (sscanf(text, "\\a%d", alignment) == 1) {
+ text += 2;
+ }
+
+ if (sscanf(text, "\\an%d", alignment) == 1) {
+ text += 3;
+ if ((*alignment) > 6) (*alignment) = (*alignment) - 2;
+ else if ((*alignment) > 3) (*alignment) = (*alignment) + 5;
+ }
+ }
+
+ if (*text == '}') in_tag = 0;
+ }
+
+ text++;
+ }
+
+
+ /* check alignment validity */
+ if ((*alignment) < 1 || (*alignment) > 11) {
+ (*alignment) = 2;
+ }
+
+ /* convert to window coordinates */
+ if ((*sub_x) >= 0 && (*sub_y) >= 0) {
+ (*sub_x) = this->video2wnd.x + this->video2wnd.dx * (*sub_x);
+ (*sub_y) = this->video2wnd.y + this->video2wnd.dy * (*sub_y);
+ }
+
+ /* check validity, compute max width */
+ if ( (*sub_x) < 0 || (*sub_x) >= this->width ||
+ (*sub_y) < 0 || (*sub_y) >= this->height ) {
+ (*sub_x) = -1;
+ (*sub_y) = -1;
+ (*max_width) = this->width;
+ } else {
+ switch (GET_X_ALIGNMENT(*alignment)) {
+ case ALIGN_LEFT:
+ (*max_width) = this->width - (*sub_x);
+ break;
+ case ALIGN_CENTER:
+ (*max_width) = this->width;
+ break;
+ case ALIGN_RIGHT:
+ (*max_width) = (*sub_x);
+ break;
+ }
+ }
+
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: position : (%d, %d), max width : %d, alignment : %d\n",
+ (*sub_x), (*sub_y), (*max_width), (*alignment));
+}
+
+static int is_cjk_encoding(const char *enc) {
+ /* CJK charset strings defined in iconvdata/gconv-modules of glibc */
+ static const char cjk_encoding_strings[][16] = {
+ "SJIS",
+ "CP932",
+ "EUC-KR",
+ "UHC",
+ "JOHAB",
+ "BIG5",
+ "BIG5HKSCS",
+ "EUC-JP-MS",
+ "EUC-JP",
+ "EUC-CN",
+ "GBBIG5",
+ "GBK",
+ "GBGBK",
+ "EUC-TW",
+ "ISO-2022-JP",
+ "ISO-2022-JP-2",
+ "ISO-2022-JP-3",
+ "ISO-2022-KR",
+ "ISO-2022-CN",
+ "ISO-2022-CN-EXT",
+ "GB18030",
+ "EUC-JISX0213",
+ "SHIFT_JISX0213",
+ };
+
+ int pstr;
+
+ /* return 1 if encoding string is one of the CJK(Chinese,Jananese,Korean)
+ * character set strings. */
+ for (pstr = 0; pstr < sizeof (cjk_encoding_strings) / sizeof (cjk_encoding_strings[0]); pstr++)
+ if (strcasecmp (enc, cjk_encoding_strings[pstr]) == 0)
+ return 1;
+
+ return 0;
+}
+
+static void draw_subtitle(sputext_decoder_t *this, int64_t sub_start, int64_t sub_end ) {
+
+ int y;
+ int sub_x, sub_y, max_width = this->width;
+ int alignment;
+
+ _x_assert(this->renderer != NULL);
+ if ( ! this->renderer )
+ return;
+
+ read_ssa_tag(this, this->text[0], &alignment, &sub_x, &sub_y, &max_width);
+
+ update_font_size(this, 0);
+
+ const char *const font = get_font (this->class);
+ if( strcmp(this->font, font) ) {
+ strncpy(this->font, font, FILENAME_MAX);
+ this->font[FILENAME_MAX - 1] = '\0';
+ this->renderer->set_font (this->osd, font, this->font_size);
+ }
+
+ int font_size = this->font_size;
+
+ const char *const encoding = this->buf_encoding ? : this->class->src_encoding;
+ this->renderer->set_encoding(this->osd, encoding);
+
+ int rebuild_all = 0;
+ int line;
+ for (line = 0; line < this->lines; line++) {
+ int line_width = ogm_get_width(this, this->text[line]);
+
+ /* line too long */
+ if (line_width > max_width) {
+ char *current_cut, *best_cut;
+ int a;
+
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: Line too long: %d > %d, split at max size.\n",
+ line_width, max_width);
+
+ /* can't fit with keeping existing lines */
+ if (this->lines + 1 > SUB_MAX_TEXT) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: Can't fit with keeping existing line, we have to rebuild all the subtitle\n");
+ rebuild_all = 1;
+ break;
+ }
+
+ /* find the longest sequence witch fit */
+ line_width = 0;
+ current_cut = this->text[line];
+ best_cut = NULL;
+ while (line_width < max_width) {
+ while (*current_cut && *current_cut != ' ') current_cut++;
+ if (*current_cut == ' ') {
+ *current_cut = 0;
+ line_width = ogm_get_width(this, this->text[line]);
+ *current_cut = ' ';
+ if (line_width < max_width) best_cut = current_cut;
+ current_cut++;
+ } else {
+ break; /* end of line */
+ }
+ }
+
+ if (best_cut == NULL) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: Can't wrap line: a word is too long, abort.\n");
+ break;
+ }
+
+ /* move other lines */
+ for (a = this->lines - 1; a > line; a--)
+ memcpy(this->text[a + 1], this->text[a], SUB_BUFSIZE);
+
+ /* split current one */
+ strncpy(this->text[line + 1], best_cut + 1, SUB_BUFSIZE);
+ *best_cut = 0;
+
+ this->lines = this->lines + 1;
+ }
+ }
+
+ /* regenerate all the lines to find something that better fits */
+ if (rebuild_all) {
+ char buf[SUB_BUFSIZE * SUB_MAX_TEXT] = { 0, };
+
+ int line;
+ for(line = 0; line < this->lines; line++) {
+ const size_t len = strlen(buf);
+ if (len)
+ buf[len] = ' ';
+
+ strncat(buf, this->text[line], SUB_BUFSIZE-len-1);
+ }
+
+ char *stream = buf;
+ this->lines = 0;
+
+ char *current_cut, *best_cut;
+ do {
+
+ if (this->lines + 1 < SUB_MAX_TEXT) {
+
+ /* find the longest sequence witch fit */
+ int line_width = 0;
+ current_cut = stream;
+ best_cut = NULL;
+ while (line_width < max_width) {
+ while (*current_cut && *current_cut != ' ') current_cut++;
+ if (*current_cut == ' ') {
+ *current_cut = 0;
+ line_width = ogm_get_width(this, stream);
+ *current_cut = ' ';
+ if (line_width < max_width) best_cut = current_cut;
+ current_cut++;
+ } else {
+ line_width = ogm_get_width(this, stream);
+ if (line_width < max_width) best_cut = current_cut;
+ break; /* end of line */
+ }
+ }
+ }
+
+ /* line maybe too long, but we have reached last subtitle line */
+ else {
+ best_cut = current_cut = stream + strlen(stream);
+ }
+
+ /* copy current line */
+ if (best_cut != NULL) *best_cut = 0;
+ strncpy(this->text[this->lines], stream, SUB_BUFSIZE);
+ this->lines = this->lines + 1;
+
+ stream = best_cut + 1;
+
+ } while (best_cut != current_cut);
+
+ }
+
+
+ /* Erase subtitle : use last_y and last_lines saved last turn. */
+ if (this->last_lines) {
+ this->renderer->filled_rect (this->osd, 0, this->last_y,
+ this->width - 1, this->last_y + this->last_lines * this->line_height,
+ 0);
+ }
+
+ switch (GET_Y_ALIGNMENT(alignment)) {
+ case ALIGN_TOP:
+ if (sub_y >= 0) y = sub_y;
+ else y = 5;
+ break;
+
+ case ALIGN_MIDDLE:
+ if (sub_y >= 0) y = sub_y - (this->lines * this->line_height) / 2;
+ else y = (this->height - this->lines * this->line_height) / 2;
+ break;
+
+ case ALIGN_BOTTOM:
+ default:
+ if (sub_y >= 0) y = sub_y - this->lines * this->line_height;
+ else y = this->height - this->lines * this->line_height - this->class->vertical_offset;
+ break;
+ }
+ if (y < 0 || y >= this->height)
+ y = this->height - this->line_height * this->lines;
+
+ this->last_lines = this->lines;
+ this->last_y = y;
+
+
+ for (line = 0; line < this->lines; line++) {
+ int w, x;
+
+ while(1) {
+ w = ogm_get_width( this, this->text[line]);
+
+ switch (GET_X_ALIGNMENT(alignment)) {
+ case ALIGN_LEFT:
+ if (sub_x >= 0) x = sub_x;
+ else x = 5;
+ break;
+
+ case ALIGN_RIGHT:
+ if (sub_x >= 0) x = sub_x - w;
+ else x = max_width - w - 5;
+ break;
+
+ case ALIGN_CENTER:
+ default:
+ if (sub_x >= 0) x = sub_x - w / 2;
+ else x = (max_width - w) / 2;
+ break;
+ }
+
+
+ if( w > max_width && font_size > 16 ) {
+ font_size -= 4;
+ this->renderer->set_font (this->osd, get_font (this->class), font_size);
+ } else {
+ break;
+ }
+ }
+
+ if( is_cjk_encoding(encoding) ) {
+ this->renderer->render_text (this->osd, x, y + line * this->line_height,
+ this->text[line], OSD_TEXT1);
+ } else {
+ ogm_render_line(this, x, y + line*this->line_height, this->text[line]);
+ }
+ }
+
+ if( font_size != this->font_size )
+ this->renderer->set_font (this->osd, get_font (this->class), this->font_size);
+
+ if( this->last_subtitle_end && sub_start < this->last_subtitle_end ) {
+ sub_start = this->last_subtitle_end;
+ }
+ this->last_subtitle_end = sub_end;
+
+ this->renderer->set_text_palette (this->osd, -1, OSD_TEXT1);
+ this->renderer->get_palette(this->osd, this->spu_palette, this->spu_trans);
+ /* append some colors for colored typeface tag */
+ memcpy(this->spu_palette+OSD_TEXT2, sub_palette, sizeof(sub_palette));
+ memcpy(this->spu_trans+OSD_TEXT2, sub_trans, sizeof(sub_trans));
+ this->renderer->set_palette(this->osd, this->spu_palette, this->spu_trans);
+
+ if (this->unscaled)
+ this->renderer->show_unscaled (this->osd, sub_start);
+ else
+ this->renderer->show (this->osd, sub_start);
+
+ this->renderer->hide (this->osd, sub_end);
+
+ lprintf ("scheduling subtitle >%s< at %"PRId64" until %"PRId64", current time is %"PRId64"\n",
+ this->text[0], sub_start, sub_end,
+ this->stream->xine->clock->get_current_time (this->stream->xine->clock));
+}
+
+
+static void spudec_decode_data (spu_decoder_t *this_gen, buf_element_t *buf) {
+
+ sputext_decoder_t *this = (sputext_decoder_t *) this_gen;
+ int uses_time;
+ int32_t start, end, diff;
+ int64_t start_vpts, end_vpts;
+ int64_t spu_offset;
+ int i;
+ uint32_t *val;
+ char *str;
+ extra_info_t extra_info;
+ int master_status, slave_status;
+ int vo_discard;
+
+ /* filter unwanted streams */
+ if (buf->decoder_flags & BUF_FLAG_HEADER) {
+ return;
+ }
+ if (buf->decoder_flags & BUF_FLAG_PREVIEW)
+ return;
+
+ if ((this->stream->spu_channel & 0x1f) != (buf->type & 0x1f))
+ return;
+
+ if ( (buf->decoder_flags & BUF_FLAG_SPECIAL) &&
+ (buf->decoder_info[1] == BUF_SPECIAL_CHARSET_ENCODING) )
+ this->buf_encoding = buf->decoder_info_ptr[2];
+ else
+ this->buf_encoding = NULL;
+
+ this->current_osd_text = OSD_TEXT1;
+
+ if( (buf->type & 0xFFFF0000) == BUF_SPU_OGM ) {
+
+ this->ogm = 1;
+ uses_time = 1;
+ val = (uint32_t * )buf->content;
+ start = *val++;
+ end = *val++;
+ str = (char *)val;
+
+ if (!*str) return;
+ /* Empty ogm packets (as created by ogmmux) clears out old messages. We already respect the end time. */
+
+ this->lines = 0;
+
+ i = 0;
+ while (*str && (this->lines < SUB_MAX_TEXT) && (i < SUB_BUFSIZE)) {
+ if (*str == '\r' || *str == '\n') {
+ if (i) {
+ this->text[ this->lines ][i] = 0;
+ this->lines++;
+ i = 0;
+ }
+ } else {
+ this->text[ this->lines ][i] = *str;
+ if (i < SUB_BUFSIZE-1)
+ i++;
+ }
+ str++;
+ }
+ if (i == SUB_BUFSIZE)
+ i--;
+
+ if (i) {
+ this->text[ this->lines ][i] = 0;
+ this->lines++;
+ }
+
+ } else {
+
+ this->ogm = 0;
+ val = (uint32_t * )buf->content;
+
+ this->lines = *val++;
+ uses_time = *val++;
+ start = *val++;
+ end = *val++;
+ str = (char *)val;
+ for (i = 0; i < this->lines; i++, str += strlen(str) + 1) {
+ strncpy( this->text[i], str, SUB_BUFSIZE - 1);
+ this->text[i][SUB_BUFSIZE - 1] = '\0';
+ }
+
+ }
+
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: decoder data [%s]\n", this->text[0]);
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: mode %d timing %d->%d\n", uses_time, start, end);
+
+ if( end <= start ) {
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: discarding subtitle with invalid timing\n");
+ return;
+ }
+
+ spu_offset = this->stream->master->metronom->get_option (this->stream->master->metronom,
+ METRONOM_SPU_OFFSET);
+ if( uses_time ) {
+ start += (spu_offset / 90);
+ end += (spu_offset / 90);
+ } else {
+ if( this->osd && this->img_duration ) {
+ start += spu_offset / this->img_duration;
+ end += spu_offset / this->img_duration;
+ }
+ }
+
+ while( !this->finished ) {
+
+ master_status = xine_get_status (this->stream->master);
+ slave_status = xine_get_status (this->stream);
+ vo_discard = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_DISCARD_FRAMES);
+
+ _x_get_current_info (this->stream->master, &extra_info, sizeof(extra_info) );
+
+ lprintf("master: %d slave: %d input_normpos: %d vo_discard: %d\n",
+ master_status, slave_status, extra_info.input_normpos, vo_discard);
+
+ if( !this->started && (master_status == XINE_STATUS_PLAY &&
+ slave_status == XINE_STATUS_PLAY &&
+ extra_info.input_normpos) ) {
+ lprintf("started\n");
+
+ this->width = this->height = 0;
+
+ update_output_size( this );
+ if( this->width && this->height ) {
+ this->started = 1;
+ }
+ }
+
+ if( this->started ) {
+
+ if( master_status != XINE_STATUS_PLAY ||
+ slave_status != XINE_STATUS_PLAY ||
+ vo_discard ) {
+ lprintf("finished\n");
+
+ this->width = this->height = 0;
+ this->finished = 1;
+ return;
+ }
+
+ if( this->osd ) {
+
+ /* try to use frame number mode */
+ if( !uses_time && extra_info.frame_number ) {
+
+ diff = end - extra_info.frame_number;
+
+ /* discard old subtitles */
+ if( diff < 0 ) {
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: discarding old subtitle\n");
+ return;
+ }
+
+ diff = start - extra_info.frame_number;
+
+ start_vpts = extra_info.vpts + diff * this->img_duration;
+ end_vpts = start_vpts + (end-start) * this->img_duration;
+
+ } else {
+
+ if( !uses_time ) {
+ start = start * this->img_duration / 90;
+ end = end * this->img_duration / 90;
+ uses_time = 1;
+ }
+
+ diff = end - extra_info.input_time;
+
+ /* discard old subtitles */
+ if( diff < 0 ) {
+ xprintf(this->class->xine, XINE_VERBOSITY_DEBUG,
+ "libsputext: discarding old subtitle\n");
+ return;
+ }
+
+ diff = start - extra_info.input_time;
+
+ start_vpts = extra_info.vpts + diff * 90;
+ end_vpts = start_vpts + (end-start) * 90;
+ }
+
+ _x_spu_decoder_sleep(this->stream, start_vpts);
+ update_output_size( this );
+ draw_subtitle(this, start_vpts, end_vpts);
+
+ return;
+ }
+ }
+
+ if (_x_spu_decoder_sleep(this->stream, 0))
+ xine_usec_sleep (50000);
+ else
+ return;
+ }
+}
+
+
+static void spudec_reset (spu_decoder_t *this_gen) {
+ sputext_decoder_t *this = (sputext_decoder_t *) this_gen;
+
+ lprintf("i guess we just seeked\n");
+ this->width = this->height = 0;
+ this->started = this->finished = 0;
+ this->last_subtitle_end = 0;
+}
+
+static void spudec_discontinuity (spu_decoder_t *this_gen) {
+ /* sputext_decoder_t *this = (sputext_decoder_t *) this_gen; */
+
+}
+
+static void spudec_dispose (spu_decoder_t *this_gen) {
+ sputext_decoder_t *this = (sputext_decoder_t *) this_gen;
+
+ if (this->osd) {
+ this->renderer->free_object (this->osd);
+ this->osd = NULL;
+ }
+ free(this);
+}
+
+static void update_vertical_offset(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->vertical_offset = entry->num_value;
+}
+
+static void update_osd_font(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ strncpy(class->font, entry->str_value, FONTNAME_SIZE);
+ class->font[FONTNAME_SIZE - 1] = '\0';
+
+ xprintf(class->xine, XINE_VERBOSITY_DEBUG, "libsputext: spu_font = %s\n", class->font );
+}
+
+#ifdef HAVE_FT2
+static void update_osd_font_ft(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ strncpy(class->font_ft, entry->str_value, FILENAME_MAX);
+ class->font_ft[FILENAME_MAX - 1] = '\0';
+
+ xprintf(class->xine, XINE_VERBOSITY_DEBUG, "libsputext: spu_font_ft = %s\n", class->font_ft);
+}
+
+static void update_osd_use_font_ft(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->use_font_ft = entry->num_value;
+
+ xprintf(class->xine, XINE_VERBOSITY_DEBUG, "libsputext: spu_use_font_ft = %d\n", class->use_font_ft);
+}
+#endif
+
+static void update_subtitle_size(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->subtitle_size = entry->num_value;
+}
+
+static void update_use_unscaled(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->use_unscaled = entry->num_value;
+}
+
+static spu_decoder_t *sputext_class_open_plugin (spu_decoder_class_t *class_gen, xine_stream_t *stream) {
+
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+ sputext_decoder_t *this ;
+
+ this = (sputext_decoder_t *) calloc(1, sizeof(sputext_decoder_t));
+
+ this->spu_decoder.decode_data = spudec_decode_data;
+ this->spu_decoder.reset = spudec_reset;
+ this->spu_decoder.discontinuity = spudec_discontinuity;
+ this->spu_decoder.get_interact_info = NULL;
+ this->spu_decoder.set_button = NULL;
+ this->spu_decoder.dispose = spudec_dispose;
+
+ this->class = class;
+ this->stream = stream;
+
+ return (spu_decoder_t *) this;
+}
+
+static void sputext_class_dispose (spu_decoder_class_t *class_gen) {
+ sputext_class_t *this = (sputext_class_t *)class_gen;
+
+ this->xine->config->unregister_callback(this->xine->config,
+ "subtitles.separate.src_encoding");
+ this->xine->config->unregister_callback(this->xine->config,
+ "subtitles.separate.subtitle_size");
+ this->xine->config->unregister_callback(this->xine->config,
+ "subtitles.separate.vertical_offset");
+ this->xine->config->unregister_callback(this->xine->config,
+ "subtitles.separate.use_unscaled_osd");
+ free (this);
+}
+
+static void update_src_encoding(void *class_gen, xine_cfg_entry_t *entry)
+{
+ sputext_class_t *class = (sputext_class_t *)class_gen;
+
+ class->src_encoding = entry->str_value;
+ xprintf(class->xine, XINE_VERBOSITY_DEBUG, "libsputext: spu_src_encoding = %s\n", class->src_encoding );
+}
+
+static void *init_spu_decoder_plugin (xine_t *xine, void *data) {
+
+ static const char *const subtitle_size_strings[] = {
+ "tiny", "small", "normal", "large", "very large", "huge", NULL
+ };
+ sputext_class_t *this ;
+
+ lprintf("init class\n");
+
+ this = (sputext_class_t *) calloc(1, sizeof(sputext_class_t));
+
+ this->class.open_plugin = sputext_class_open_plugin;
+ this->class.identifier = "sputext";
+ this->class.description = N_("external subtitle decoder plugin");
+ this->class.dispose = sputext_class_dispose;
+
+ this->xine = xine;
+
+ this->subtitle_size = xine->config->register_enum(xine->config,
+ "subtitles.separate.subtitle_size",
+ 1,
+ subtitle_size_strings,
+ _("subtitle size"),
+ _("You can adjust the subtitle size here. The setting will "
+ "be evaluated relative to the window size."),
+ 0, update_subtitle_size, this);
+ this->vertical_offset = xine->config->register_num(xine->config,
+ "subtitles.separate.vertical_offset",
+ 0,
+ _("subtitle vertical offset"),
+ _("You can adjust the vertical position of the subtitle. "
+ "The setting will be evaluated relative to the window size."),
+ 0, update_vertical_offset, this);
+ strncpy(this->font, xine->config->register_string(xine->config,
+ "subtitles.separate.font",
+ "sans",
+ _("font for subtitles"),
+ _("A font from the xine font directory to be used for the "
+ "subtitle text."),
+ 10, update_osd_font, this), FONTNAME_SIZE);
+ this->font[FONTNAME_SIZE - 1] = '\0';
+#ifdef HAVE_FT2
+ strncpy(this->font_ft, xine->config->register_filename(xine->config,
+ "subtitles.separate.font_freetype",
+ "", XINE_CONFIG_STRING_IS_FILENAME,
+ _("font for subtitles"),
+ _("An outline font file (e.g. a .ttf) to be used for the subtitle text."),
+ 10, update_osd_font_ft, this), FILENAME_MAX);
+ this->font_ft[FILENAME_MAX - 1] = '\0';
+ this->use_font_ft = xine->config->register_bool(xine->config,
+ "subtitles.separate.font_use_freetype",
+ 0,
+ _("whether to use a freetype font"),
+ NULL,
+ 10, update_osd_use_font_ft, this);
+#endif
+ this->src_encoding = xine->config->register_string(xine->config,
+ "subtitles.separate.src_encoding",
+ xine_guess_spu_encoding(),
+ _("encoding of the subtitles"),
+ _("The encoding of the subtitle text in the stream. This setting "
+ "is used to render non-ASCII characters correctly. If non-ASCII "
+ "characters are not displayed as you expect, ask the "
+ "creator of the subtitles what encoding was used."),
+ 10, update_src_encoding, this);
+ this->use_unscaled = xine->config->register_bool(xine->config,
+ "subtitles.separate.use_unscaled_osd",
+ 1,
+ _("use unscaled OSD if possible"),
+ _("The unscaled OSD will be rendered independently of the video "
+ "frame and will always be sharp, even if the video is magnified. "
+ "This will look better, but does not work with all graphics "
+ "hardware. The alternative is the scaled OSD, which will become "
+ "blurry, if you enlarge a low resolution video to fullscreen, but "
+ "it works with all graphics cards."),
+ 10, update_use_unscaled, this);
+
+ return &this->class;
+}
+
+
+/* plugin catalog information */
+static const uint32_t supported_types[] = { BUF_SPU_TEXT, BUF_SPU_OGM, 0 };
+
+static const decoder_info_t spudec_info = {
+ supported_types, /* supported types */
+ 1 /* priority */
+};
+
+extern void *init_sputext_demux_class (xine_t *xine, void *data);
+
+const plugin_info_t xine_plugin_info[] EXPORTED = {
+ /* type, API, "name", version, special_info, init_function */
+ { PLUGIN_SPU_DECODER | PLUGIN_MUST_PRELOAD, 17, "sputext", XINE_VERSION_CODE, &spudec_info, &init_spu_decoder_plugin },
+ { PLUGIN_DEMUX, 27, "sputext", XINE_VERSION_CODE, NULL, &init_sputext_demux_class },
+ { PLUGIN_NONE, 0, "", 0, NULL, NULL }
+};
diff --git a/src/spu_dec/sputext_demuxer.c b/src/spu_dec/sputext_demuxer.c
new file mode 100644
index 000000000..a8e252c30
--- /dev/null
+++ b/src/spu_dec/sputext_demuxer.c
@@ -0,0 +1,1452 @@
+/*
+ * Copyright (C) 2000-2003 the xine project
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * code based on old libsputext/xine_decoder.c
+ *
+ * code based on mplayer module:
+ *
+ * Subtitle reader with format autodetection
+ *
+ * Written by laaz
+ * Some code cleanup & realloc() by A'rpi/ESP-team
+ * dunnowhat sub format by szabi
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#define LOG_MODULE "demux_sputext"
+#define LOG_VERBOSE
+/*
+#define LOG
+*/
+
+#include <xine/xine_internal.h>
+#include <xine/xineutils.h>
+#include <xine/demux.h>
+
+#define ERR (void *)-1
+#define SUB_MAX_TEXT 5
+#define SUB_BUFSIZE 1024
+#define LINE_LEN 1000
+#define LINE_LEN_QUOT "1000"
+
+/*
+ * Demuxer typedefs
+ */
+
+typedef struct {
+
+ int lines;
+
+ long start; /* csecs */
+ long end; /* csecs */
+
+ char *text[SUB_MAX_TEXT];
+
+} subtitle_t;
+
+
+typedef struct {
+
+ demux_plugin_t demux_plugin;
+ xine_stream_t *stream;
+ input_plugin_t *input;
+
+ int status;
+
+ char buf[SUB_BUFSIZE];
+ off_t buflen;
+
+ float mpsub_position;
+
+ int uses_time;
+ int errs;
+ subtitle_t *subtitles;
+ int num; /* number of subtitle structs */
+ int cur; /* current subtitle */
+ int format; /* constants see below */
+ char next_line[SUB_BUFSIZE]; /* a buffer for next line read from file */
+
+} demux_sputext_t;
+
+typedef struct demux_sputext_class_s {
+
+ demux_class_t demux_class;
+
+ int max_timeout; /* default timeout of hidding subtitles */
+
+} demux_sputext_class_t;
+
+/*
+ * Demuxer code start
+ */
+
+#define FORMAT_UNKNOWN -1
+#define FORMAT_MICRODVD 0
+#define FORMAT_SUBRIP 1
+#define FORMAT_SUBVIEWER 2
+#define FORMAT_SAMI 3
+#define FORMAT_VPLAYER 4
+#define FORMAT_RT 5
+#define FORMAT_SSA 6 /* Sub Station Alpha */
+#define FORMAT_PJS 7
+#define FORMAT_MPSUB 8
+#define FORMAT_AQTITLE 9
+#define FORMAT_JACOBSUB 10
+#define FORMAT_SUBVIEWER2 11
+#define FORMAT_SUBRIP09 12
+#define FORMAT_MPL2 13 /*Mplayer sub 2 ?*/
+
+static int eol(char p) {
+ return (p=='\r' || p=='\n' || p=='\0');
+}
+
+static inline void trail_space(char *s) {
+ while (isspace(*s)) {
+ char *copy = s;
+ do {
+ copy[0] = copy[1];
+ copy++;
+ } while(*copy);
+ }
+ size_t i = strlen(s) - 1;
+ while (i > 0 && isspace(s[i]))
+ s[i--] = '\0';
+}
+
+/*
+ * Reimplementation of fgets() using the input->read() method.
+ */
+static char *read_line_from_input(demux_sputext_t *this, char *line, off_t len) {
+ off_t nread = 0;
+ char *s;
+ int linelen;
+
+ if ((len - this->buflen) > 512 && len < SUB_BUFSIZE) {
+ if((nread = this->input->read(this->input,
+ &this->buf[this->buflen], len - this->buflen)) < 0) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, "read failed.\n");
+ return NULL;
+ }
+ }
+
+ this->buflen += nread;
+ this->buf[this->buflen] = '\0';
+
+ s = strchr(this->buf, '\n');
+
+ if (line && (s || this->buflen)) {
+
+ linelen = s ? (s - this->buf) + 1 : this->buflen;
+
+ memcpy(line, this->buf, linelen);
+ line[linelen] = '\0';
+
+ memmove(this->buf, &this->buf[linelen], SUB_BUFSIZE - linelen);
+ this->buflen -= linelen;
+
+ return line;
+ }
+
+ return NULL;
+}
+
+
+static subtitle_t *sub_read_line_sami(demux_sputext_t *this, subtitle_t *current) {
+
+ static char line[LINE_LEN + 1];
+ static char *s = NULL;
+ char text[LINE_LEN + 1], *p, *q;
+ int state;
+
+ p = NULL;
+ current->lines = current->start = 0;
+ current->end = -1;
+ state = 0;
+
+ /* read the first line */
+ if (!s)
+ if (!(s = read_line_from_input(this, line, LINE_LEN))) return 0;
+
+ do {
+ switch (state) {
+
+ case 0: /* find "START=" */
+ s = strstr (s, "Start=");
+ if (s) {
+ current->start = strtol (s + 6, &s, 0) / 10;
+ state = 1; continue;
+ }
+ break;
+
+ case 1: /* find "<P" */
+ if ((s = strstr (s, "<P"))) { s += 2; state = 2; continue; }
+ break;
+
+ case 2: /* find ">" */
+ if ((s = strchr (s, '>'))) { s++; state = 3; p = text; continue; }
+ break;
+
+ case 3: /* get all text until '<' appears */
+ if (*s == '\0') { break; }
+ else if (*s == '<') { state = 4; }
+ else if (!strncasecmp (s, "&nbsp;", 6)) { *p++ = ' '; s += 6; }
+ else if (*s == '\r') { s++; }
+ else if (!strncasecmp (s, "<br>", 4) || *s == '\n') {
+ *p = '\0'; p = text; trail_space (text);
+ if (text[0] != '\0')
+ current->text[current->lines++] = strdup (text);
+ if (*s == '\n') s++; else s += 4;
+ }
+ else *p++ = *s++;
+ continue;
+
+ case 4: /* get current->end or skip <TAG> */
+ q = strstr (s, "Start=");
+ if (q) {
+ current->end = strtol (q + 6, &q, 0) / 10 - 1;
+ *p = '\0'; trail_space (text);
+ if (text[0] != '\0')
+ current->text[current->lines++] = strdup (text);
+ if (current->lines > 0) { state = 99; break; }
+ state = 0; continue;
+ }
+ s = strchr (s, '>');
+ if (s) { s++; state = 3; continue; }
+ break;
+ }
+
+ /* read next line */
+ if (state != 99 && !(s = read_line_from_input (this, line, LINE_LEN)))
+ return 0;
+
+ } while (state != 99);
+
+ return current;
+}
+
+
+static char *sub_readtext(char *source, char **dest) {
+ int len=0;
+ char *p=source;
+
+ while ( !eol(*p) && *p!= '|' ) {
+ p++,len++;
+ }
+
+ *dest = strndup(source, len);
+
+ while (*p=='\r' || *p=='\n' || *p=='|')
+ p++;
+
+ if (*p) return p; /* not-last text field */
+ else return NULL; /* last text field */
+}
+
+static subtitle_t *sub_read_line_microdvd(demux_sputext_t *this, subtitle_t *current) {
+
+ char line[LINE_LEN + 1];
+ char line2[LINE_LEN + 1];
+ char *p, *next;
+ int i;
+
+ memset (current, 0, sizeof(subtitle_t));
+
+ current->end=-1;
+ do {
+ if (!read_line_from_input (this, line, LINE_LEN)) return NULL;
+ } while ((sscanf (line, "{%ld}{}%" LINE_LEN_QUOT "[^\r\n]", &(current->start), line2) !=2) &&
+ (sscanf (line, "{%ld}{%ld}%" LINE_LEN_QUOT "[^\r\n]", &(current->start), &(current->end),line2) !=3)
+ );
+
+ p=line2;
+
+ next=p, i=0;
+ while ((next =sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR) return ERR;
+ i++;
+ if (i>=SUB_MAX_TEXT) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Too many lines in a subtitle\n");
+ current->lines=i;
+ return current;
+ }
+ }
+ current->lines= ++i;
+
+ return current;
+}
+
+static subtitle_t *sub_read_line_subviewer(demux_sputext_t *this, subtitle_t *current) {
+
+ char line[LINE_LEN + 1];
+ int a1,a2,a3,a4,b1,b2,b3,b4;
+ char *p=NULL, *q=NULL;
+ int len;
+
+ memset (current, 0, sizeof(subtitle_t));
+
+ while (1) {
+ if (!read_line_from_input(this, line, LINE_LEN)) return NULL;
+ if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4) < 8) {
+ if (sscanf (line, "%d:%d:%d,%d,%d:%d:%d,%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4) < 8)
+ continue;
+ }
+ current->start = a1*360000+a2*6000+a3*100+a4;
+ current->end = b1*360000+b2*6000+b3*100+b4;
+
+ if (!read_line_from_input(this, line, LINE_LEN))
+ return NULL;
+
+ p=q=line;
+ for (current->lines=1; current->lines <= SUB_MAX_TEXT; current->lines++) {
+ for (q=p,len=0; *p && *p!='\r' && *p!='\n' && *p!='|' && strncasecmp(p,"[br]",4); p++,len++);
+ current->text[current->lines-1] = strndup(q, len);
+ if (!current->text[current->lines-1]) return ERR;
+ if (!*p || *p=='\r' || *p=='\n') break;
+ if (*p=='[') while (*p++!=']');
+ if (*p=='|') p++;
+ }
+ if (current->lines > SUB_MAX_TEXT) current->lines = SUB_MAX_TEXT;
+ break;
+ }
+ return current;
+}
+
+static subtitle_t *sub_read_line_subrip(demux_sputext_t *this,subtitle_t *current) {
+ char line[LINE_LEN + 1];
+ int a1,a2,a3,a4,b1,b2,b3,b4;
+ int i,end_sub;
+
+ memset(current,0,sizeof(subtitle_t));
+ do {
+ if(!read_line_from_input(this,line,LINE_LEN))
+ return NULL;
+ i = sscanf(line,"%d:%d:%d%*[,.]%d --> %d:%d:%d%*[,.]%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4);
+ } while(i < 8);
+ current->start = a1*360000+a2*6000+a3*100+a4/10;
+ current->end = b1*360000+b2*6000+b3*100+b4/10;
+ i=0;
+ end_sub=0;
+ do {
+ char *p; /* pointer to the curently read char */
+ char temp_line[SUB_BUFSIZE]; /* subtitle line that will be transfered to current->text[i] */
+ int temp_index; /* ... and its index wich 'points' to the first EMPTY place -> last read char is at temp_index-1 if temp_index>0 */
+ temp_line[SUB_BUFSIZE-1]='\0'; /* just in case... */
+ if(!read_line_from_input(this,line,LINE_LEN)) {
+ if(i)
+ break; /* if something was read, transmit it */
+ else
+ return NULL; /* if not, repport EOF */
+ }
+ for(temp_index=0,p=line;*p!='\0' && !end_sub && temp_index<SUB_BUFSIZE && i<SUB_MAX_TEXT;p++) {
+ switch(*p) {
+ case '\\':
+ if(*(p+1)=='N' || *(p+1)=='n') {
+ temp_line[temp_index++]='\0'; /* end of curent line */
+ p++;
+ } else
+ temp_line[temp_index++]=*p;
+ break;
+ case '\r': /* just ignore '\r's */
+ break;
+ case '\n':
+ temp_line[temp_index++]='\0';
+ break;
+ default:
+ temp_line[temp_index++]=*p;
+ break;
+ }
+ if(temp_index>0) {
+ if(temp_index==SUB_BUFSIZE)
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Too many characters in a subtitle line\n");
+ if(temp_line[temp_index-1]=='\0' || temp_index==SUB_BUFSIZE) {
+ if(temp_index>1) { /* more than 1 char (including '\0') -> that is a valid one */
+ /* temp_index<=SUB_BUFSIZE is always true here */
+ current->text[i] = strndup(temp_line, temp_index);
+ if(!current->text[i])
+ return ERR;
+ i++;
+ temp_index=0;
+ } else
+ end_sub=1;
+ }
+ }
+ }
+ } while(i<SUB_MAX_TEXT && !end_sub);
+ if(i>=SUB_MAX_TEXT)
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Too many lines in a subtitle\n");
+ current->lines=i;
+ return current;
+}
+
+static subtitle_t *sub_read_line_vplayer(demux_sputext_t *this,subtitle_t *current) {
+ char line[LINE_LEN + 1];
+ int a1,a2,a3,b1,b2,b3;
+ char *p=NULL, *next, *p2;
+ int i;
+
+ memset (current, 0, sizeof(subtitle_t));
+
+ while (!current->text[0]) {
+ if( this->next_line[0] == '\0' ) { /* if the buffer is empty.... */
+ if( !read_line_from_input(this, line, LINE_LEN) ) return NULL;
+ } else {
+ /* ... get the current line from buffer. */
+ strncpy( line, this->next_line, LINE_LEN);
+ line[LINE_LEN] = '\0'; /* I'm scared. This makes me feel better. */
+ this->next_line[0] = '\0'; /* mark the buffer as empty. */
+ }
+ /* Initialize buffer with next line */
+ if( ! read_line_from_input( this, this->next_line, LINE_LEN) ) {
+ this->next_line[0] = '\0';
+ return NULL;
+ }
+ if( (sscanf( line, "%d:%d:%d:", &a1, &a2, &a3) < 3) ||
+ (sscanf( this->next_line, "%d:%d:%d:", &b1, &b2, &b3) < 3) )
+ continue;
+ current->start = a1*360000+a2*6000+a3*100;
+ current->end = b1*360000+b2*6000+b3*100;
+ if ((current->end - current->start) > LINE_LEN)
+ current->end = current->start + LINE_LEN; /* not too long though. */
+ /* teraz czas na wkopiowanie stringu */
+ p=line;
+ /* finds the body of the subtitle_t */
+ for (i=0; i<3; i++){
+ p2=strchr( p, ':');
+ if( p2 == NULL ) break;
+ p=p2+1;
+ }
+
+ next=p;
+ i=0;
+ while( (next = sub_readtext( next, &(current->text[i]))) ) {
+ if (current->text[i]==ERR)
+ return ERR;
+ i++;
+ if (i>=SUB_MAX_TEXT) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Too many lines in a subtitle\n");
+ current->lines=i;
+ return current;
+ }
+ }
+ current->lines=++i;
+ }
+ return current;
+}
+
+static subtitle_t *sub_read_line_rt(demux_sputext_t *this,subtitle_t *current) {
+ /*
+ * TODO: This format uses quite rich (sub/super)set of xhtml
+ * I couldn't check it since DTD is not included.
+ * WARNING: full XML parses can be required for proper parsing
+ */
+ char line[LINE_LEN + 1];
+ int a1,a2,a3,a4,b1,b2,b3,b4;
+ char *p=NULL,*next=NULL;
+ int i,len,plen;
+
+ memset (current, 0, sizeof(subtitle_t));
+
+ while (!current->text[0]) {
+ if (!read_line_from_input(this, line, LINE_LEN)) return NULL;
+ /*
+ * TODO: it seems that format of time is not easily determined, it may be 1:12, 1:12.0 or 0:1:12.0
+ * to describe the same moment in time. Maybe there are even more formats in use.
+ */
+ if ((len=sscanf (line, "<Time Begin=\"%d:%d:%d.%d\" End=\"%d:%d:%d.%d\"",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4)) < 8)
+
+ plen=a1=a2=a3=a4=b1=b2=b3=b4=0;
+ if (
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d\" %*[Ee]nd=\"%d:%d\"%*[^<]<clear/>%n",&a2,&a3,&b2,&b3,&plen)) < 4) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d\" %*[Ee]nd=\"%d:%d.%d\"%*[^<]<clear/>%n",&a2,&a3,&b2,&b3,&b4,&plen)) < 5) &&
+ /* ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d.%d\" %*[Ee]nd=\"%d:%d\"%*[^<]<clear/>%n",&a2,&a3,&a4,&b2,&b3,&plen)) < 5) && */
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d.%d\" %*[Ee]nd=\"%d:%d.%d\"%*[^<]<clear/>%n",&a2,&a3,&a4,&b2,&b3,&b4,&plen)) < 6) &&
+ ((len=sscanf (line, "<%*[tT]ime %*[bB]egin=\"%d:%d:%d.%d\" %*[Ee]nd=\"%d:%d:%d.%d\"%*[^<]<clear/>%n",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4,&plen)) < 8)
+ )
+ continue;
+ current->start = a1*360000+a2*6000+a3*100+a4/10;
+ current->end = b1*360000+b2*6000+b3*100+b4/10;
+ p=line; p+=plen;i=0;
+ /* TODO: I don't know what kind of convention is here for marking multiline subs, maybe <br/> like in xml? */
+ next = strstr(line,"<clear/>")+8;i=0;
+ while ((next =sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR)
+ return ERR;
+ i++;
+ if (i>=SUB_MAX_TEXT) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Too many lines in a subtitle\n");
+ current->lines=i;
+ return current;
+ }
+ }
+ current->lines=i+1;
+ }
+ return current;
+}
+
+static subtitle_t *sub_read_line_ssa(demux_sputext_t *this,subtitle_t *current) {
+ int comma;
+ static int max_comma = 32; /* let's use 32 for the case that the */
+ /* amount of commas increase with newer SSA versions */
+
+ int hour1, min1, sec1, hunsec1, hour2, min2, sec2, hunsec2, nothing;
+ int num;
+ char line[LINE_LEN + 1], line3[LINE_LEN + 1], *line2;
+ char *tmp;
+
+ do {
+ if (!read_line_from_input(this, line, LINE_LEN)) return NULL;
+ } while (sscanf (line, "Dialogue: Marked=%d,%d:%d:%d.%d,%d:%d:%d.%d,"
+ "%[^\n\r]", &nothing,
+ &hour1, &min1, &sec1, &hunsec1,
+ &hour2, &min2, &sec2, &hunsec2,
+ line3) < 9
+ &&
+ sscanf (line, "Dialogue: %d,%d:%d:%d.%d,%d:%d:%d.%d,"
+ "%[^\n\r]", &nothing,
+ &hour1, &min1, &sec1, &hunsec1,
+ &hour2, &min2, &sec2, &hunsec2,
+ line3) < 9 );
+
+ line2=strchr(line3, ',');
+ if (!line2)
+ return NULL;
+
+ for (comma = 4; comma < max_comma; comma ++)
+ {
+ tmp = line2;
+ if(!(tmp=strchr(++tmp, ','))) break;
+ if(*(++tmp) == ' ') break;
+ /* a space after a comma means we're already in a sentence */
+ line2 = tmp;
+ }
+
+ if(comma < max_comma)max_comma = comma;
+ /* eliminate the trailing comma */
+ if(*line2 == ',') line2++;
+
+ current->lines=0;num=0;
+ current->start = 360000*hour1 + 6000*min1 + 100*sec1 + hunsec1;
+ current->end = 360000*hour2 + 6000*min2 + 100*sec2 + hunsec2;
+
+ while (((tmp=strstr(line2, "\\n")) != NULL) || ((tmp=strstr(line2, "\\N")) != NULL) ){
+ current->text[num] = strndup(line2, tmp-line2);
+ line2=tmp+2;
+ num++;
+ current->lines++;
+ if (current->lines >= SUB_MAX_TEXT) return current;
+ }
+
+ current->text[num]=strdup(line2);
+ current->lines++;
+
+ return current;
+}
+
+/* Sylvain "Skarsnik" Colinet <scolinet@gmail.com>
+ * From MPlayer subreader.c :
+ *
+ * PJS subtitles reader.
+ * That's the "Phoenix Japanimation Society" format.
+ * I found some of them in http://www.scriptsclub.org/ (used for anime).
+ * The time is in tenths of second.
+ *
+ * by set, based on code by szabi (dunnowhat sub format ;-)
+ */
+
+static subtitle_t *sub_read_line_pjs (demux_sputext_t *this, subtitle_t *current) {
+ char line[LINE_LEN + 1];
+ char text[LINE_LEN + 1];
+ char *s, *d;
+
+ memset (current, 0, sizeof(subtitle_t));
+
+ if (!read_line_from_input(this, line, LINE_LEN))
+ return NULL;
+ for (s = line; *s && isspace(*s); s++);
+ if (*s == 0)
+ return NULL;
+ if (sscanf (line, "%ld,%ld,", &(current->start),
+ &(current->end)) <2)
+ return ERR;
+ /* the files I have are in tenths of second */
+ current->start *= 10;
+ current->end *= 10;
+
+ /* walk to the beggining of the string */
+ for (; *s; s++) if (*s==',') break;
+ if (*s) {
+ for (s++; *s; s++) if (*s==',') break;
+ if (*s) s++;
+ }
+ if (*s!='"') {
+ return ERR;
+ }
+ /* copy the string to the text buffer */
+ for (s++, d=text; *s && *s!='"'; s++, d++)
+ *d=*s;
+ *d=0;
+ current->text[0] = strdup(text);
+ current->lines = 1;
+
+ return current;
+}
+
+static subtitle_t *sub_read_line_mpsub (demux_sputext_t *this, subtitle_t *current) {
+ char line[LINE_LEN + 1];
+ float a,b;
+ int num=0;
+ char *p, *q;
+
+ do {
+ if (!read_line_from_input(this, line, LINE_LEN))
+ return NULL;
+ } while (sscanf (line, "%f %f", &a, &b) !=2);
+
+ this->mpsub_position += (a*100.0);
+ current->start = (int) this->mpsub_position;
+ this->mpsub_position += (b*100.0);
+ current->end = (int) this->mpsub_position;
+
+ while (num < SUB_MAX_TEXT) {
+ if (!read_line_from_input(this, line, LINE_LEN))
+ return NULL;
+
+ p=line;
+ while (isspace(*p))
+ p++;
+
+ if (eol(*p) && num > 0)
+ return current;
+
+ if (eol(*p))
+ return NULL;
+
+ for (q=p; !eol(*q); q++);
+ *q='\0';
+ if (strlen(p)) {
+ current->text[num]=strdup(p);
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, ">%s<\n",p);
+ current->lines = ++num;
+ } else {
+ if (num)
+ return current;
+ else
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static subtitle_t *sub_read_line_aqt (demux_sputext_t *this, subtitle_t *current) {
+ char line[LINE_LEN + 1];
+
+ memset (current, 0, sizeof(subtitle_t));
+
+ while (1) {
+ /* try to locate next subtitle_t */
+ if (!read_line_from_input(this, line, LINE_LEN))
+ return NULL;
+ if (!(sscanf (line, "-->> %ld", &(current->start)) <1))
+ break;
+ }
+
+ if (!read_line_from_input(this, line, LINE_LEN))
+ return NULL;
+
+ sub_readtext((char *) &line,&current->text[0]);
+ current->lines = 1;
+ current->end = -1;
+
+ if (!read_line_from_input(this, line, LINE_LEN))
+ return current;;
+
+ sub_readtext((char *) &line,&current->text[1]);
+ current->lines = 2;
+
+ if ((current->text[0][0]==0) && (current->text[1][0]==0)) {
+ return NULL;
+ }
+
+ return current;
+}
+
+static subtitle_t *sub_read_line_jacobsub(demux_sputext_t *this, subtitle_t *current) {
+ char line1[LINE_LEN], line2[LINE_LEN], directive[LINE_LEN], *p, *q;
+ unsigned a1, a2, a3, a4, b1, b2, b3, b4, comment = 0;
+ static unsigned jacoTimeres = 30;
+ static int jacoShift = 0;
+
+ memset(current, 0, sizeof(subtitle_t));
+ memset(line1, 0, LINE_LEN);
+ memset(line2, 0, LINE_LEN);
+ memset(directive, 0, LINE_LEN);
+ while (!current->text[0]) {
+ if (!read_line_from_input(this, line1, LINE_LEN)) {
+ return NULL;
+ }
+ if (sscanf
+ (line1, "%u:%u:%u.%u %u:%u:%u.%u %" LINE_LEN_QUOT "[^\n\r]", &a1, &a2, &a3, &a4,
+ &b1, &b2, &b3, &b4, line2) < 9) {
+ if (sscanf(line1, "@%u @%u %" LINE_LEN_QUOT "[^\n\r]", &a4, &b4, line2) < 3) {
+ if (line1[0] == '#') {
+ int hours = 0, minutes = 0, seconds, delta, inverter =
+ 1;
+ unsigned units = jacoShift;
+ switch (line1[1]) {
+ case 'S':
+ case 's':
+ if (isalpha(line1[2])) {
+ delta = 6;
+ } else {
+ delta = 2;
+ }
+ if (sscanf(&line1[delta], "%d", &hours)) {
+ if (hours < 0) {
+ hours *= -1;
+ inverter = -1;
+ }
+ if (sscanf(&line1[delta], "%*d:%d", &minutes)) {
+ if (sscanf
+ (&line1[delta], "%*d:%*d:%d",
+ &seconds)) {
+ sscanf(&line1[delta], "%*d:%*d:%*d.%d",
+ &units);
+ } else {
+ hours = 0;
+ sscanf(&line1[delta], "%d:%d.%d",
+ &minutes, &seconds, &units);
+ minutes *= inverter;
+ }
+ } else {
+ hours = minutes = 0;
+ sscanf(&line1[delta], "%d.%d", &seconds,
+ &units);
+ seconds *= inverter;
+ }
+ jacoShift =
+ ((hours * 3600 + minutes * 60 +
+ seconds) * jacoTimeres +
+ units) * inverter;
+ }
+ break;
+ case 'T':
+ case 't':
+ if (isalpha(line1[2])) {
+ delta = 8;
+ } else {
+ delta = 2;
+ }
+ sscanf(&line1[delta], "%u", &jacoTimeres);
+ break;
+ }
+ }
+ continue;
+ } else {
+ current->start =
+ (unsigned long) ((a4 + jacoShift) * 100.0 /
+ jacoTimeres);
+ current->end =
+ (unsigned long) ((b4 + jacoShift) * 100.0 /
+ jacoTimeres);
+ }
+ } else {
+ current->start =
+ (unsigned
+ long) (((a1 * 3600 + a2 * 60 + a3) * jacoTimeres + a4 +
+ jacoShift) * 100.0 / jacoTimeres);
+ current->end =
+ (unsigned
+ long) (((b1 * 3600 + b2 * 60 + b3) * jacoTimeres + b4 +
+ jacoShift) * 100.0 / jacoTimeres);
+ }
+ current->lines = 0;
+ p = line2;
+ while ((*p == ' ') || (*p == '\t')) {
+ ++p;
+ }
+ if (isalpha(*p)||*p == '[') {
+ if (sscanf(p, "%s %" LINE_LEN_QUOT "[^\n\r]", directive, line1) < 2)
+ return ERR;
+ if ((strcasestr(directive, "RDB") != NULL)
+ || (strcasestr(directive, "RDC") != NULL)
+ || (strcasestr(directive, "RLB") != NULL)
+ || (strcasestr(directive, "RLG") != NULL)) {
+ continue;
+ }
+ /* no alignment */
+#if 0
+ if (strcasestr(directive, "JL") != NULL) {
+ current->alignment = SUB_ALIGNMENT_HLEFT;
+ } else if (strcasestr(directive, "JR") != NULL) {
+ current->alignment = SUB_ALIGNMENT_HRIGHT;
+ } else {
+ current->alignment = SUB_ALIGNMENT_HCENTER;
+ }
+#endif
+ strcpy(line2, line1);
+ p = line2;
+ }
+ for (q = line1; (!eol(*p)) && (current->lines < SUB_MAX_TEXT); ++p) {
+ switch (*p) {
+ case '{':
+ comment++;
+ break;
+ case '}':
+ if (comment) {
+ --comment;
+ /* the next line to get rid of a blank after the comment */
+ if ((*(p + 1)) == ' ')
+ p++;
+ }
+ break;
+ case '~':
+ if (!comment) {
+ *q = ' ';
+ ++q;
+ }
+ break;
+ case ' ':
+ case '\t':
+ if ((*(p + 1) == ' ') || (*(p + 1) == '\t'))
+ break;
+ if (!comment) {
+ *q = ' ';
+ ++q;
+ }
+ break;
+ case '\\':
+ if (*(p + 1) == 'n') {
+ *q = '\0';
+ q = line1;
+ current->text[current->lines++] = strdup(line1);
+ ++p;
+ break;
+ }
+ if ((*(p + 1) == 'C') || (*(p + 1) == 'c') ||
+ (*(p + 1) == 'F') || (*(p + 1) == 'f')) {
+ ++p,++p;
+ break;
+ }
+ if ((*(p + 1) == 'B') || (*(p + 1) == 'b') ||
+ /* actually this means "insert current date here" */
+ (*(p + 1) == 'D') ||
+ (*(p + 1) == 'I') || (*(p + 1) == 'i') ||
+ (*(p + 1) == 'N') ||
+ /* actually this means "insert current time here" */
+ (*(p + 1) == 'T') ||
+ (*(p + 1) == 'U') || (*(p + 1) == 'u')) {
+ ++p;
+ break;
+ }
+ if ((*(p + 1) == '\\') ||
+ (*(p + 1) == '~') || (*(p + 1) == '{')) {
+ ++p;
+ } else if (eol(*(p + 1))) {
+ if (!read_line_from_input(this, directive, LINE_LEN))
+ return NULL;
+ trail_space(directive);
+ strncat(line2, directive,
+ ((LINE_LEN > 511) ? LINE_LEN-1 : 511)
+ - strlen(line2));
+ break;
+ }
+ default:
+ if (!comment) {
+ *q = *p;
+ ++q;
+ }
+ }
+ }
+ *q = '\0';
+ if (current->lines < SUB_MAX_TEXT)
+ current->text[current->lines] = strdup(line1);
+ else
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Too many lines in a subtitle\n");
+ }
+ current->lines++;
+ return current;
+}
+
+static subtitle_t *sub_read_line_subviewer2(demux_sputext_t *this, subtitle_t *current) {
+ char line[LINE_LEN+1];
+ int a1,a2,a3,a4;
+ char *p=NULL;
+ int i,len;
+
+ while (!current->text[0]) {
+ if (!read_line_from_input(this, line, LINE_LEN)) return NULL;
+ if (line[0]!='{')
+ continue;
+ if ((len=sscanf (line, "{T %d:%d:%d:%d",&a1,&a2,&a3,&a4)) < 4)
+ continue;
+ current->start = a1*360000+a2*6000+a3*100+a4/10;
+ for (i=0; i<SUB_MAX_TEXT;) {
+ if (!read_line_from_input(this, line, LINE_LEN)) break;
+ if (line[0]=='}') break;
+ len=0;
+ for (p=line; *p!='\n' && *p!='\r' && *p; ++p,++len);
+ if (len) {
+ current->text[i] = strndup(line, len);
+ if (!current->text[i]) return ERR;
+ ++i;
+ } else {
+ break;
+ }
+ }
+ current->lines=i;
+ }
+ return current;
+}
+
+static subtitle_t *sub_read_line_subrip09 (demux_sputext_t *this, subtitle_t *current) {
+ char line[LINE_LEN + 1];
+ char *next;
+ int h, m, s;
+ int i;
+
+ memset (current, 0, sizeof(subtitle_t));
+
+ do {
+ if (!read_line_from_input (this, line, LINE_LEN)) return NULL;
+ } while (sscanf (line, "[%d:%d:%d]", &h, &m, &s) != 3);
+
+ if (!read_line_from_input (this, line, LINE_LEN)) return NULL;
+
+ current->start = 360000 * h + 6000 * m + 100 * s;
+ current->end = -1;
+
+ next=line;
+ i=0;
+ while ((next = sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i]==ERR) return ERR;
+ i++;
+ if (i>=SUB_MAX_TEXT) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Too many lines in a subtitle\n");
+ current->lines=i;
+ return current;
+ }
+ }
+ current->lines= ++i;
+
+ return current;
+}
+
+/* Code from subreader.c of MPlayer
+** Sylvain "Skarsnik" Colinet <scolinet@gmail.com>
+*/
+
+static subtitle_t *sub_read_line_mpl2(demux_sputext_t *this, subtitle_t *current) {
+ char line[LINE_LEN+1];
+ char line2[LINE_LEN+1];
+ char *p, *next;
+ int i;
+
+ memset (current, 0, sizeof(subtitle_t));
+ do {
+ if (!read_line_from_input (this, line, LINE_LEN)) return NULL;
+ } while ((sscanf (line,
+ "[%ld][%ld]%[^\r\n]",
+ &(current->start), &(current->end), line2) < 3));
+ current->start *= 10;
+ current->end *= 10;
+ p=line2;
+
+ next=p, i=0;
+ while ((next = sub_readtext (next, &(current->text[i])))) {
+ if (current->text[i] == ERR) {return ERR;}
+ i++;
+ if (i >= SUB_MAX_TEXT) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, "Too many lines in a subtitle\n");
+ current->lines = i;
+ return current;
+ }
+ }
+ current->lines= ++i;
+
+ return current;
+}
+
+
+
+static int sub_autodetect (demux_sputext_t *this) {
+
+ char line[LINE_LEN + 1];
+ int i, j=0;
+ char p;
+
+ while (j < 100) {
+ j++;
+ if (!read_line_from_input(this, line, LINE_LEN))
+ return FORMAT_UNKNOWN;
+
+ if ((sscanf (line, "{%d}{}", &i)==1) ||
+ (sscanf (line, "{%d}{%d}", &i, &i)==2)) {
+ this->uses_time=0;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "microdvd subtitle format detected\n");
+ return FORMAT_MICRODVD;
+ }
+
+ if (sscanf (line, "%d:%d:%d%*[,.]%d --> %d:%d:%d%*[,.]%d", &i, &i, &i, &i, &i, &i, &i, &i)==8) {
+ this->uses_time=1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "subrip subtitle format detected\n");
+ return FORMAT_SUBRIP;
+ }
+
+ if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d", &i, &i, &i, &i, &i, &i, &i, &i)==8){
+ this->uses_time=1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "subviewer subtitle format detected\n");
+ return FORMAT_SUBVIEWER;
+ }
+
+ if (sscanf (line, "%d:%d:%d,%d,%d:%d:%d,%d", &i, &i, &i, &i, &i, &i, &i, &i)==8){
+ this->uses_time=1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "subviewer subtitle format detected\n");
+ return FORMAT_SUBVIEWER;
+ }
+
+ if (strstr (line, "<SAMI>")) {
+ this->uses_time=1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "sami subtitle format detected\n");
+ return FORMAT_SAMI;
+ }
+ if (sscanf (line, "%d:%d:%d:", &i, &i, &i )==3) {
+ this->uses_time=1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "vplayer subtitle format detected\n");
+ return FORMAT_VPLAYER;
+ }
+ /*
+ * A RealText format is a markup language, starts with <window> tag,
+ * options (behaviour modifiers) are possible.
+ */
+ if ( !strcasecmp(line, "<window") ) {
+ this->uses_time=1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "rt subtitle format detected\n");
+ return FORMAT_RT;
+ }
+ if ((!memcmp(line, "Dialogue: Marked", 16)) || (!memcmp(line, "Dialogue: ", 10))) {
+ this->uses_time=1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "ssa subtitle format detected\n");
+ return FORMAT_SSA;
+ }
+ if (sscanf (line, "%d,%d,\"%c", &i, &i, (char *) &i) == 3) {
+ this->uses_time=0;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "pjs subtitle format detected\n");
+ return FORMAT_PJS;
+ }
+ if (sscanf (line, "FORMAT=%d", &i) == 1) {
+ this->uses_time=0;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "mpsub subtitle format detected\n");
+ return FORMAT_MPSUB;
+ }
+ if (sscanf (line, "FORMAT=TIM%c", &p)==1 && p=='E') {
+ this->uses_time=1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "mpsub subtitle format detected\n");
+ return FORMAT_MPSUB;
+ }
+ if (strstr (line, "-->>")) {
+ this->uses_time=0;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "aqtitle subtitle format detected\n");
+ return FORMAT_AQTITLE;
+ }
+ if (sscanf(line, "@%d @%d", &i, &i) == 2 ||
+ sscanf(line, "%d:%d:%d.%d %d:%d:%d.%d", &i, &i, &i, &i, &i, &i, &i, &i) == 8) {
+ this->uses_time = 1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "jacobsub subtitle format detected\n");
+ return FORMAT_JACOBSUB;
+ }
+ if (sscanf(line, "{T %d:%d:%d:%d",&i, &i, &i, &i) == 4) {
+ this->uses_time = 1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "subviewer 2.0 subtitle format detected\n");
+ return FORMAT_SUBVIEWER2;
+ }
+ if (sscanf(line, "[%d:%d:%d]", &i, &i, &i) == 3) {
+ this->uses_time = 1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "subrip 0.9 subtitle format detected\n");
+ return FORMAT_SUBRIP09;
+ }
+
+ if (sscanf (line, "[%d][%d]", &i, &i) == 2) {
+ this->uses_time = 1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "mpl2 subtitle format detected\n");
+ return FORMAT_MPL2;
+ }
+ }
+ return FORMAT_UNKNOWN; /* too many bad lines */
+}
+
+static subtitle_t *sub_read_file (demux_sputext_t *this) {
+
+ int n_max;
+ int timeout;
+ subtitle_t *first;
+ subtitle_t * (*func[])(demux_sputext_t *this,subtitle_t *dest)=
+ {
+ sub_read_line_microdvd,
+ sub_read_line_subrip,
+ sub_read_line_subviewer,
+ sub_read_line_sami,
+ sub_read_line_vplayer,
+ sub_read_line_rt,
+ sub_read_line_ssa,
+ sub_read_line_pjs,
+ sub_read_line_mpsub,
+ sub_read_line_aqt,
+ sub_read_line_jacobsub,
+ sub_read_line_subviewer2,
+ sub_read_line_subrip09,
+ sub_read_line_mpl2,
+ };
+
+ /* Rewind (sub_autodetect() needs to read input from the beginning) */
+ if(this->input->seek(this->input, 0, SEEK_SET) == -1) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "seek failed.\n");
+ return NULL;
+ }
+ this->buflen = 0;
+
+ this->format=sub_autodetect (this);
+ if (this->format==FORMAT_UNKNOWN) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Could not determine file format\n");
+ return NULL;
+ }
+
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Detected subtitle file format: %d\n",this->format);
+
+ /* Rewind */
+ if(this->input->seek(this->input, 0, SEEK_SET) == -1) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "seek failed.\n");
+ return NULL;
+ }
+ this->buflen = 0;
+
+ this->num=0;n_max=32;
+ first = calloc(n_max, sizeof(subtitle_t));
+ if(!first) return NULL;
+ timeout = ((demux_sputext_class_t *)
+ (this->demux_plugin.demux_class))->max_timeout;
+ if (this->uses_time) timeout *= 100;
+ else timeout *= 10;
+
+ while(1) {
+ subtitle_t *sub;
+
+ if(this->num>=n_max){
+ n_max+=16;
+ first=realloc(first,n_max*sizeof(subtitle_t));
+ }
+
+ sub = func[this->format] (this, &first[this->num]);
+
+ if (!sub)
+ break; /* EOF */
+
+ if (sub==ERR)
+ ++this->errs;
+ else {
+ if (this->num > 0 && first[this->num-1].end == -1) {
+ /* end time not defined in the subtitle */
+ if (timeout > 0) {
+ /* timeout */
+ if (timeout > sub->start - first[this->num-1].start) {
+ first[this->num-1].end = sub->start;
+ } else
+ first[this->num-1].end = first[this->num-1].start + timeout;
+ } else {
+ /* no timeout */
+ first[this->num-1].end = sub->start;
+ }
+ }
+ ++this->num; /* Error vs. Valid */
+ }
+ }
+ /* timeout of last subtitle */
+ if (this->num > 0 && first[this->num-1].end == -1)
+ if (timeout > 0) {
+ first[this->num-1].end = first[this->num-1].start + timeout;
+ }
+
+ if(this->stream->xine->verbosity >= XINE_VERBOSITY_DEBUG) {
+ char buffer[1024];
+
+ sprintf(buffer, "Read %i subtitles", this->num);
+
+ if(this->errs)
+ sprintf(buffer + strlen(buffer), ", %i bad line(s).\n", this->errs);
+ else
+ strcat(buffer, "\n");
+
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "%s", buffer);
+ }
+
+ return first;
+}
+
+static int demux_sputext_next (demux_sputext_t *this_gen) {
+ demux_sputext_t *this = (demux_sputext_t *) this_gen;
+ buf_element_t *buf;
+ uint32_t *val;
+ char *str;
+ subtitle_t *sub;
+ int line;
+
+ if (this->cur >= this->num)
+ return 0;
+
+ sub = &this->subtitles[this->cur];
+
+ buf = this->stream->video_fifo->buffer_pool_alloc(this->stream->video_fifo);
+ buf->type = BUF_SPU_TEXT;
+ buf->pts = 0;
+
+ val = (uint32_t * )buf->content;
+ *val++ = sub->lines;
+ *val++ = this->uses_time;
+ *val++ = (this->uses_time) ? sub->start * 10 : sub->start;
+ *val++ = (this->uses_time) ? sub->end * 10 : sub->end;
+ str = (char *)val;
+ for (line = 0; line < sub->lines; line++, str+=strlen(str)+1) {
+ strncpy(str, sub->text[line], SUB_BUFSIZE-1);
+ str[SUB_BUFSIZE-1] = '\0';
+ }
+
+ this->stream->video_fifo->put(this->stream->video_fifo, buf);
+ this->cur++;
+
+ return 1;
+}
+
+static void demux_sputext_dispose (demux_plugin_t *this_gen) {
+ demux_sputext_t *this = (demux_sputext_t *) this_gen;
+ int i, l;
+
+ for (i = 0; i < this->num; i++) {
+ for (l = 0; l < this->subtitles[i].lines; l++)
+ free(this->subtitles[i].text[l]);
+ }
+ free(this->subtitles);
+ free(this);
+}
+
+static int demux_sputext_get_status (demux_plugin_t *this_gen) {
+ demux_sputext_t *this = (demux_sputext_t *) this_gen;
+ return this->status;
+}
+
+static int demux_sputext_get_stream_length (demux_plugin_t *this_gen) {
+ demux_sputext_t *this = (demux_sputext_t *) this_gen;
+
+ if( this->uses_time && this->num ) {
+ return this->subtitles[this->num-1].end * 10;
+ } else {
+ return 0;
+ }
+}
+
+static int demux_sputext_send_chunk (demux_plugin_t *this_gen) {
+ demux_sputext_t *this = (demux_sputext_t *) this_gen;
+
+ if (!demux_sputext_next (this)) {
+ this->status = DEMUX_FINISHED;
+ }
+
+ return this->status;
+}
+
+static int demux_sputext_seek (demux_plugin_t *this_gen,
+ off_t start_pos, int start_time, int playing) {
+ demux_sputext_t *this = (demux_sputext_t*)this_gen;
+
+ lprintf("seek() called\n");
+
+ /* simple seeking approach: just go back to start.
+ * decoder will discard subtitles until the desired position.
+ */
+ this->cur = 0;
+ this->status = DEMUX_OK;
+
+ _x_demux_flush_engine (this->stream);
+ _x_demux_control_newpts(this->stream, 0, 0);
+
+ return this->status;
+}
+
+static void demux_sputext_send_headers(demux_plugin_t *this_gen) {
+ demux_sputext_t *this = (demux_sputext_t*)this_gen;
+ buf_element_t *buf;
+
+
+ lprintf("send_headers() called\n");
+
+ _x_demux_control_start(this->stream);
+ _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO, 0);
+ _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, 0);
+
+ /* enable the SPU channel */
+ buf = this->stream->video_fifo->buffer_pool_alloc(this->stream->video_fifo);
+ buf->type = BUF_CONTROL_SPU_CHANNEL;
+ buf->decoder_info[0] = buf->decoder_info[1] = buf->decoder_info[2] = 0;
+ this->stream->video_fifo->put(this->stream->video_fifo, buf);
+
+ this->status = DEMUX_OK;
+}
+
+static uint32_t demux_sputext_get_capabilities(demux_plugin_t *this_gen) {
+ return DEMUX_CAP_NOCAP;
+}
+
+static int demux_sputext_get_optional_data(demux_plugin_t *this_gen,
+ void *data, int data_type) {
+ int channel = *((int *)data);
+
+ switch (data_type) {
+ case DEMUX_OPTIONAL_DATA_SPULANG:
+ if (channel == -1 || channel == 0) {
+ strcpy(data, "sub");
+ return DEMUX_OPTIONAL_SUCCESS;
+ }
+ default:
+ return DEMUX_OPTIONAL_UNSUPPORTED;
+ }
+}
+
+static demux_plugin_t *open_demux_plugin (demux_class_t *class_gen, xine_stream_t *stream,
+ input_plugin_t *input_gen) {
+
+ input_plugin_t *input = (input_plugin_t *) input_gen;
+ demux_sputext_t *this;
+
+ lprintf("open_plugin() called\n");
+
+ this = calloc(1, sizeof (demux_sputext_t));
+ this->stream = stream;
+ this->input = input;
+
+ this->demux_plugin.send_headers = demux_sputext_send_headers;
+ this->demux_plugin.send_chunk = demux_sputext_send_chunk;
+ this->demux_plugin.seek = demux_sputext_seek;
+ this->demux_plugin.dispose = demux_sputext_dispose;
+ this->demux_plugin.get_status = demux_sputext_get_status;
+ this->demux_plugin.get_stream_length = demux_sputext_get_stream_length;
+ this->demux_plugin.get_capabilities = demux_sputext_get_capabilities;
+ this->demux_plugin.get_optional_data = demux_sputext_get_optional_data;
+ this->demux_plugin.demux_class = class_gen;
+
+ this->buflen = 0;
+
+ switch (stream->content_detection_method) {
+ case METHOD_BY_MRL:
+ {
+ const char *const mrl = input->get_mrl(input);
+ const char *const ending = strrchr(mrl, '.');
+
+ if (!ending || (
+ (strncasecmp(ending, ".asc", 4) != 0) &&
+ (strncasecmp(ending, ".txt", 4) != 0) &&
+ (strncasecmp(ending, ".sub", 4) != 0) &&
+ (strncasecmp(ending, ".srt", 4) != 0) &&
+ (strncasecmp(ending, ".smi", 4) != 0) &&
+ (strncasecmp(ending, ".ssa", 4) != 0) &&
+ (strncasecmp(ending, ".ass", 4) != 0))) {
+ free (this);
+ return NULL;
+ }
+ }
+ /* falling through is intended */
+
+ case METHOD_EXPLICIT:
+ /* case METHOD_BY_CONTENT: */
+
+ /* FIXME: for now this demuxer only works when requested explicitly
+ * to make sure it does not interfere with others;
+ * If this is found too inconvenient, this may be changed after making
+ * sure the content detection does not produce any false positives.
+ */
+
+ if ((input->get_capabilities(input) & INPUT_CAP_SEEKABLE) != 0) {
+
+ this->subtitles = sub_read_file (this);
+
+ this->cur = 0;
+
+ if (this->subtitles) {
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "subtitle format %s time.\n",
+ this->uses_time ? "uses" : "doesn't use");
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG,
+ "read %i subtitles, %i errors.\n", this->num, this->errs);
+ return &this->demux_plugin;
+ }
+ }
+ /* falling through is intended */
+ }
+
+ free (this);
+ return NULL;
+}
+
+static void config_timeout_cb(void *this_gen, xine_cfg_entry_t *entry) {
+ demux_sputext_class_t *this = (demux_sputext_class_t *)this_gen;
+
+ this->max_timeout = entry->num_value;
+}
+
+void *init_sputext_demux_class (xine_t *xine, void *data) {
+
+ demux_sputext_class_t *this ;
+
+ lprintf("initializing\n");
+
+ this = calloc(1, sizeof (demux_sputext_class_t));
+
+ this->demux_class.open_plugin = open_demux_plugin;
+ this->demux_class.description = N_("sputext demuxer plugin");
+ this->demux_class.identifier = "sputext";
+ /* do not report this mimetype, it might confuse browsers. */
+ /* "text/plain: asc txt sub srt: VIDEO subtitles;" */
+ this->demux_class.mimetypes = NULL;
+ this->demux_class.extensions = "asc txt sub srt smi ssa ass";
+ this->demux_class.dispose = default_demux_class_dispose;
+
+ /*
+ * Some subtitling formats, namely AQT and Subrip09, define the end of a
+ * subtitle as the beginning of the following. From end-user view it's
+ * better define timeout of hidding. Setting to zero means "no timeout".
+ */
+ this->max_timeout = xine->config->register_num(xine->config,
+ "subtitles.separate.timeout", 4,
+ _("default duration of subtitle display in seconds"),
+ _("Some subtitle formats do not explicitly give a duration for each subtitle. "
+ "For these, you can set a default duration here. Setting to zero will result "
+ "in the subtitle being shown until the next one takes over."),
+ 20, config_timeout_cb, this);
+
+ return this;
+}
diff --git a/src/spu_dec/xine_cc_decoder.c b/src/spu_dec/xine_cc_decoder.c
new file mode 100644
index 000000000..fc69304cc
--- /dev/null
+++ b/src/spu_dec/xine_cc_decoder.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2000-2008 the xine project
+ *
+ * This file is part of xine, a free video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+ *
+ * closed caption spu decoder. receive data by events.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <xine/buffer.h>
+#include <xine/xine_internal.h>
+#include <xine/xineutils.h>
+#include "cc_decoder.h"
+
+/*
+#define LOG_DEBUG 1
+*/
+
+static const char *const cc_schemes[NUM_CC_PALETTES + 1] = {
+ "White/Gray/Translucent",
+ "White/Black/Solid",
+ NULL
+};
+
+typedef struct spucc_decoder_s {
+ spu_decoder_t spu_decoder;
+
+ xine_stream_t *stream;
+
+ /* closed captioning decoder state */
+ cc_decoder_t *ccdec;
+ /* true if ccdec has been initialized */
+ int cc_open;
+
+ /* closed captioning decoder configuration and intrinsics */
+ cc_state_t cc_state;
+ /* this is to detect configuration changes */
+ int config_version;
+
+ /* video dimensions captured in frame change events */
+ int video_width;
+ int video_height;
+
+ /* events will be sent here */
+ xine_event_queue_t *queue;
+
+} spucc_decoder_t;
+
+
+/*------------------- general utility functions ----------------------------*/
+
+static void copy_str(char *d, const char *s, size_t maxbytes)
+{
+ strncpy(d, s, maxbytes - 1);
+ d[maxbytes - 1] = '\0';
+}
+
+
+/*------------------- private methods --------------------------------------*/
+
+static void spucc_update_intrinsics(spucc_decoder_t *this)
+{
+#ifdef LOG_DEBUG
+ printf("spucc: update_intrinsics\n");
+#endif
+
+ if (this->cc_open)
+ cc_renderer_update_cfg(this->cc_state.renderer, this->video_width,
+ this->video_height);
+}
+
+static void spucc_do_close(spucc_decoder_t *this)
+{
+ if (this->cc_open) {
+#ifdef LOG_DEBUG
+ printf("spucc: close\n");
+#endif
+ cc_decoder_close(this->ccdec);
+ cc_renderer_close(this->cc_state.renderer);
+ this->cc_open = 0;
+ }
+}
+
+static void spucc_do_init (spucc_decoder_t *this)
+{
+ if (! this->cc_open) {
+#ifdef LOG_DEBUG
+ printf("spucc: init\n");
+#endif
+ /* initialize caption renderer */
+ this->cc_state.renderer = cc_renderer_open(this->stream->osd_renderer,
+ this->stream->metronom,
+ &this->cc_state,
+ this->video_width,
+ this->video_height);
+ spucc_update_intrinsics(this);
+ /* initialize CC decoder */
+ this->ccdec = cc_decoder_open(&this->cc_state);
+ this->cc_open = 1;
+ }
+}
+
+
+/*----------------- configuration listeners --------------------------------*/
+
+static void spucc_cfg_enable_change(void *this_gen, xine_cfg_entry_t *value)
+{
+ spucc_class_t *this = (spucc_class_t *) this_gen;
+ cc_config_t *cc_cfg = &this->cc_cfg;
+
+ cc_cfg->cc_enabled = value->num_value;
+#ifdef LOG_DEBUG
+ printf("spucc: closed captions are now %s.\n", cc_cfg->cc_enabled?
+ "enabled" : "disabled");
+#endif
+ cc_cfg->config_version++;
+}
+
+
+static void spucc_cfg_scheme_change(void *this_gen, xine_cfg_entry_t *value)
+{
+ spucc_class_t *this = (spucc_class_t *) this_gen;
+ cc_config_t *cc_cfg = &this->cc_cfg;
+
+ cc_cfg->cc_scheme = value->num_value;
+#ifdef LOG_DEBUG
+ printf("spucc: closed captioning scheme is now %s.\n",
+ cc_schemes[cc_cfg->cc_scheme]);
+#endif
+ cc_cfg->config_version++;
+}
+
+
+static void spucc_font_change(void *this_gen, xine_cfg_entry_t *value)
+{
+ spucc_class_t *this = (spucc_class_t *) this_gen;
+ cc_config_t *cc_cfg = &this->cc_cfg;
+ char *font;
+
+ if (strcmp(value->key, "subtitles.closedcaption.font") == 0)
+ font = cc_cfg->font;
+ else
+ font = cc_cfg->italic_font;
+
+ copy_str(font, value->str_value, CC_FONT_MAX);
+#ifdef LOG_DEBUG
+ printf("spucc: changing %s to font %s\n", value->key, font);
+#endif
+ cc_cfg->config_version++;
+}
+
+
+static void spucc_num_change(void *this_gen, xine_cfg_entry_t *value)
+{
+ spucc_class_t *this = (spucc_class_t *) this_gen;
+ cc_config_t *cc_cfg = &this->cc_cfg;
+ int *num;
+
+ if (strcmp(value->key, "subtitles.closedcaption.font_size") == 0)
+ num = &cc_cfg->font_size;
+ else
+ num = &cc_cfg->center;
+
+ *num = value->num_value;
+#ifdef LOG_DEBUG
+ printf("spucc: changing %s to %d\n", value->key, *num);
+#endif
+ cc_cfg->config_version++;
+}
+
+
+static void spucc_register_cfg_vars(spucc_class_t *this,
+ config_values_t *xine_cfg) {
+ cc_config_t *cc_vars = &this->cc_cfg;
+
+ cc_vars->cc_enabled = xine_cfg->register_bool(xine_cfg,
+ "subtitles.closedcaption.enabled", 0,
+ _("display closed captions in MPEG-2 streams"),
+ _("Closed Captions are subtitles mostly meant "
+ "to help the hearing impaired."),
+ 0, spucc_cfg_enable_change, this);
+
+ cc_vars->cc_scheme = xine_cfg->register_enum(xine_cfg,
+ "subtitles.closedcaption.scheme", 0,
+ (char **)cc_schemes,
+ _("closed-captioning foreground/background scheme"),
+ _("Choose your favourite rendering of the closed "
+ "captions."),
+ 10, spucc_cfg_scheme_change, this);
+
+ copy_str(cc_vars->font,
+ xine_cfg->register_string(xine_cfg, "subtitles.closedcaption.font", "cc",
+ _("standard closed captioning font"),
+ _("Choose the font for standard closed captions text."),
+ 20, spucc_font_change, this),
+ CC_FONT_MAX);
+
+ copy_str(cc_vars->italic_font,
+ xine_cfg->register_string(xine_cfg, "subtitles.closedcaption.italic_font", "cci",
+ _("italic closed captioning font"),
+ _("Choose the font for italic closed captions text."),
+ 20, spucc_font_change, this),
+ CC_FONT_MAX);
+
+ cc_vars->font_size = xine_cfg->register_num(xine_cfg, "subtitles.closedcaption.font_size",
+ 24,
+ _("closed captioning font size"),
+ _("Choose the font size for closed captions text."),
+ 10, spucc_num_change, this);
+
+ cc_vars->center = xine_cfg->register_bool(xine_cfg, "subtitles.closedcaption.center", 1,
+ _("center-adjust closed captions"),
+ _("When enabled, closed captions will be positioned "
+ "by the center of the individual lines."),
+ 20, spucc_num_change, this);
+}
+
+
+/* called when the video frame size changes */
+static void spucc_notify_frame_change(spucc_decoder_t *this,
+ int width, int height) {
+#ifdef LOG_DEBUG
+ printf("spucc: new frame size: %dx%d\n", width, height);
+#endif
+
+ this->video_width = width;
+ this->video_height = height;
+ spucc_update_intrinsics(this);
+}
+
+
+/*------------------- implementation of spudec interface -------------------*/
+
+static void spudec_decode_data (spu_decoder_t *this_gen, buf_element_t *buf) {
+ spucc_decoder_t *this = (spucc_decoder_t *) this_gen;
+ xine_event_t *event;
+
+ while ((event = xine_event_get(this->queue))) {
+ switch (event->type) {
+ case XINE_EVENT_FRAME_FORMAT_CHANGE:
+ {
+ xine_format_change_data_t *frame_change =
+ (xine_format_change_data_t *)event->data;
+
+ spucc_notify_frame_change(this, frame_change->width,
+ frame_change->height);
+ }
+ break;
+ }
+ xine_event_free(event);
+ }
+
+ if (buf->decoder_flags & BUF_FLAG_PREVIEW) {
+ } else {
+
+ if (this->cc_state.cc_cfg->config_version > this->config_version) {
+ spucc_update_intrinsics(this);
+ if (!this->cc_state.cc_cfg->cc_enabled)
+ spucc_do_close(this);
+ this->config_version = this->cc_state.cc_cfg->config_version;
+ }
+
+ if (this->cc_state.cc_cfg->cc_enabled) {
+ if( !this->cc_open )
+ spucc_do_init (this);
+ if(this->cc_state.can_cc) {
+ decode_cc(this->ccdec, buf->content, buf->size,
+ buf->pts);
+ }
+ }
+ }
+}
+
+static void spudec_reset (spu_decoder_t *this_gen) {
+}
+
+static void spudec_discontinuity (spu_decoder_t *this_gen) {
+}
+
+static void spudec_dispose (spu_decoder_t *this_gen) {
+ spucc_decoder_t *this = (spucc_decoder_t *) this_gen;
+
+ spucc_do_close(this);
+ xine_event_dispose_queue(this->queue);
+ free (this);
+}
+
+
+static spu_decoder_t *spudec_open_plugin (spu_decoder_class_t *class, xine_stream_t *stream) {
+
+ spucc_decoder_t *this ;
+
+ this = (spucc_decoder_t *) calloc(1, sizeof(spucc_decoder_t));
+
+ this->spu_decoder.decode_data = spudec_decode_data;
+ this->spu_decoder.reset = spudec_reset;
+ this->spu_decoder.discontinuity = spudec_discontinuity;
+ this->spu_decoder.dispose = spudec_dispose;
+ this->spu_decoder.get_interact_info = NULL;
+ this->spu_decoder.set_button = NULL;
+
+ this->stream = stream;
+ this->queue = xine_event_new_queue(stream);
+ this->cc_state.cc_cfg = &((spucc_class_t *)class)->cc_cfg;
+ this->config_version = 0;
+ this->cc_open = 0;
+
+ return &this->spu_decoder;
+}
+
+static void *init_spu_decoder_plugin (xine_t *xine, void *data) {
+
+ spucc_class_t *this ;
+
+ this = (spucc_class_t *) calloc(1, sizeof(spucc_class_t));
+
+ this->spu_class.open_plugin = spudec_open_plugin;
+ this->spu_class.identifier = "spucc";
+ this->spu_class.description = N_("closed caption decoder plugin");
+ this->spu_class.dispose = default_spu_decoder_class_dispose;
+
+ spucc_register_cfg_vars(this, xine->config);
+ this->cc_cfg.config_version = 0;
+
+ return &this->spu_class;
+}
+
+/* plugin catalog information */
+static const uint32_t supported_types[] = { BUF_SPU_CC, 0 };
+
+static const decoder_info_t spudec_info = {
+ supported_types, /* supported types */
+ 1 /* priority */
+};
+
+const plugin_info_t xine_plugin_info[] EXPORTED = {
+ /* type, API, "name", version, special_info, init_function */
+ { PLUGIN_SPU_DECODER, 17, "spucc", XINE_VERSION_CODE, &spudec_info, &init_spu_decoder_plugin },
+ { PLUGIN_NONE, 0, "", 0, NULL, NULL }
+};