diff options
Diffstat (limited to 'src/spu_dec')
-rw-r--r-- | src/spu_dec/Makefile.am | 42 | ||||
-rw-r--r-- | src/spu_dec/cc_decoder.c | 1517 | ||||
-rw-r--r-- | src/spu_dec/cc_decoder.h | 80 | ||||
-rw-r--r-- | src/spu_dec/cmml_decoder.c | 525 | ||||
-rw-r--r-- | src/spu_dec/nav_read.c | 1 | ||||
-rw-r--r-- | src/spu_dec/spu_decoder.c | 386 | ||||
-rw-r--r-- | src/spu_dec/spudec.c | 1034 | ||||
-rw-r--r-- | src/spu_dec/spudec.h | 146 | ||||
-rw-r--r-- | src/spu_dec/spudvb_decoder.c | 1218 | ||||
-rw-r--r-- | src/spu_dec/spuhdmv_decoder.c | 1072 | ||||
-rw-r--r-- | src/spu_dec/sputext_decoder.c | 1211 | ||||
-rw-r--r-- | src/spu_dec/sputext_demuxer.c | 1452 | ||||
-rw-r--r-- | src/spu_dec/xine_cc_decoder.c | 361 |
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, " ", 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]", ¬hing, + &hour1, &min1, &sec1, &hunsec1, + &hour2, &min2, &sec2, &hunsec2, + line3) < 9 + && + sscanf (line, "Dialogue: %d,%d:%d:%d.%d,%d:%d:%d.%d," + "%[^\n\r]", ¬hing, + &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,¤t->text[0]); + current->lines = 1; + current->end = -1; + + if (!read_line_from_input(this, line, LINE_LEN)) + return current;; + + sub_readtext((char *) &line,¤t->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 } +}; |