changeset 23510:a6c619ee9d30

Teletext support for tv:// (v4l and v4l2 only) modified patch from Otvos Attila oattila at chello dot hu Module uses zvbi library for all low-level VBI operations (like I/O with vbi device, converting vbi pages into usefull vbi_page stuctures, rendering them into RGB32 images). All teletext related stuff (except properties, slave commands and rendering osd in text mode or RGB32 rendered teletext pages in spu mode) is implemented in tvi_vbi.c New properties: teletext_page - switching between pages teletext_mode - switch between on/off/opaque/transparent modes teletext_format - (currently read-only) allows to get format info (black/white,gray,text) teletext_half_page - trivial zooming (displaying top/bottom half of teletext page) New slave commands: teletext_add_dec - user interface for jumping to any page by editing page number interactively teletext_go_link - goes though links, specified on current page
author voroshil
date Sun, 10 Jun 2007 00:06:12 +0000
parents 53d57a0ebe13
children 5c980247db1f
files Changelog DOCS/man/en/mplayer.1 DOCS/tech/MAINTAINERS DOCS/tech/slave.txt cfg-common.h command.c configure input/input.c input/input.h libvo/sub.c libvo/sub.h mpcommon.c mplayer.c spudec.c spudec.h stream/Makefile stream/tv.c stream/tv.h stream/tvi_vbi.c stream/tvi_vbi.h
diffstat 20 files changed, 1950 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Changelog	Sat Jun 09 18:20:21 2007 +0000
+++ b/Changelog	Sun Jun 10 00:06:12 2007 +0000
@@ -51,6 +51,7 @@
     * support H.264 over RTSP
     * "device" and "adevice" suboptions now works for *BSD BT848 tv driver too
     * dvdnav:// now depends on mplayer's fork of libdvdnav
+    * Teletext support for tv:// (v4l and v4l2 only)
 
     FFmpeg/libavcodec:
     * Intel Music coder audio decoder
--- a/DOCS/man/en/mplayer.1	Sat Jun 09 18:20:21 2007 +0000
+++ b/DOCS/man/en/mplayer.1	Sun Jun 10 00:06:12 2007 +0000
@@ -421,6 +421,26 @@
 .PP
 .RS
 .
+(The following keys are only valid if teletext support is enabled during
+compilation: they are used for controlling TV teletext)
+.RE
+.PP
+.PD 0
+.RS
+.IPs "X"
+Switch teletext between on, off and transparent mode.
+.IPs "Q"
+Next teletext page.
+.IPs "W"
+Previous teletext page.
+.IPs "E"
+In text mode flips top or bottom piece of page otherwise
+zooms teletext page: top or bottom or normal.
+.RE
+.PD 1
+.PP
+.RS
+.
 .TP
 .B mouse control
 .PD 0
@@ -1851,6 +1871,12 @@
 .IPs quality=<0\-100>
 Choose the quality of the JPEG compression
 (< 60 recommended for full size).
+.IPs tdevice=<value>  (default: none)
+Specify TV teletext device (example: /dev/\:vbi0).
+.IPs tformat=<text|bw|gray|color> (default: gray)
+Specify TV teletext display mode. (Note: color mode requires color SPU support.)
+.IPs tpage=<100-999> (default: 100)
+Specify starting TV teletext page number .
 .RE
 .
 .TP
--- a/DOCS/tech/MAINTAINERS	Sat Jun 09 18:20:21 2007 +0000
+++ b/DOCS/tech/MAINTAINERS	Sun Jun 10 00:06:12 2007 +0000
@@ -77,6 +77,7 @@
     * libmpdemux: Roberto Togni, Nico Sabbi
     * libmpcodecs: Roberto Togni
     * TV input/capture: Vladimir Voroshilov
+    * TV teletext: Vladimir Voroshilov
     * network streaming: Roberto Togni, Nico Sabbi, Benjamin Zores
     * DVD/VOB subtitles: None
     * config files & commandline parser: Alban Bedel
--- a/DOCS/tech/slave.txt	Sat Jun 09 18:20:21 2007 +0000
+++ b/DOCS/tech/slave.txt	Sun Jun 10 00:06:12 2007 +0000
@@ -314,6 +314,19 @@
     Toggle vsync (1 == on, 0 == off). If [value] is not provided,
     vsync status is inverted.
 
+teletext_add_dec <value>
+    On/off teletext page number editing mode and append given digit to
+    previously entered one
+    0..9 - append apropriate digit (enables editing mode if called from normal mode, and
+           switches to normal mode when third digit is entered.
+    -    - delete last digit from page number (backspace amulation, works only in page number
+           editing mode)
+
+teletext_go_link <value>
+    Follow given links on current teletext page
+    0    - go to initial page (specified by -tv tpage= parameter)
+    1..6 - follow given link
+
 tv_step_channel <channel>
     Select next/previous TV channel.
 
@@ -446,4 +459,8 @@
 tv_contrast        int       -100    100     X   X   X
 tv_saturation      int       -100    100     X   X   X
 tv_hue             int       -100    100     X   X   X
-
+teletext_page      int       100     999     X   X   X
+teletext_mode      int       0       3       X   X   X    0 - off, 1 - opaque, 2 - transparent,
+                                                          3 - transparent inverted (bw format)
+teletext_format    int       0       3       X            0 - text, 1 - b/w, 2 - gray, 3 - color
+teletext_half_page int       0       2       X   X   X    0 - off, 1 - top half, 2- bottom half
--- a/cfg-common.h	Sat Jun 09 18:20:21 2007 +0000
+++ b/cfg-common.h	Sun Jun 10 00:06:12 2007 +0000
@@ -460,6 +460,11 @@
 #endif
 	{"adevice", &tv_param_adevice, CONF_TYPE_STRING, 0, 0, 0, NULL},
 #endif
+#ifdef HAVE_TV_TELETEXT
+    {"tdevice", &tv_param_tdevice, CONF_TYPE_STRING, 0, 0, 0, NULL},
+    {"tformat", &tv_param_tformat, CONF_TYPE_STRING, 0, 0, 0, NULL},
+    {"tpage", &tv_param_tpage, CONF_TYPE_INT, CONF_RANGE, 100, 999, NULL},
+#endif
 	{"audioid", &tv_param_audio_id, CONF_TYPE_INT, CONF_RANGE, 0, 9, NULL},
 	{NULL, NULL, 0, 0, 0, 0, NULL}
 };
--- a/command.c	Sat Jun 09 18:20:21 2007 +0000
+++ b/command.c	Sun Jun 10 00:06:12 2007 +0000
@@ -1415,6 +1415,134 @@
 
 #endif
 
+#ifdef HAVE_TV_TELETEXT
+/// teletext page (RW)
+static int mp_property_teletext_page(m_option_t * prop, int action, void *arg,
+                  MPContext * mpctx)
+{
+    int val,result;
+    tvi_handle_t *tvh = mpctx->demuxer->priv;
+    if (mpctx->demuxer->type != DEMUXER_TYPE_TV || !tvh || !tvh->priv_vbi)
+        return M_PROPERTY_UNAVAILABLE;
+
+    switch (action) {
+    case M_PROPERTY_SET:
+        if (!arg)
+            return M_PROPERTY_ERROR;
+        M_PROPERTY_CLAMP(prop, *(int *) arg);
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_SET_PAGE, arg);
+	break;
+    case M_PROPERTY_GET:
+        if (!arg)
+            return M_PROPERTY_ERROR;
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_GET_PAGE, arg);
+	break;
+    case M_PROPERTY_STEP_UP:
+    case M_PROPERTY_STEP_DOWN:
+        val = (arg ? *(int *) arg : 1) * (action == M_PROPERTY_STEP_DOWN ? -1 : 1);
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_STEP_PAGE, &val);
+	break;
+    default:
+        return M_PROPERTY_NOT_IMPLEMENTED;
+    }
+    return (result==TVI_CONTROL_TRUE?M_PROPERTY_OK:M_PROPERTY_ERROR);
+}
+/// VBI teletext mode (RW)
+static int mp_property_teletext_mode(m_option_t * prop, int action, void *arg,
+                  MPContext * mpctx)
+{
+    int val,result;
+    tvi_handle_t *tvh = mpctx->demuxer->priv;
+    if (mpctx->demuxer->type != DEMUXER_TYPE_TV || !tvh || !tvh->priv_vbi)
+        return M_PROPERTY_UNAVAILABLE;
+
+    switch (action) {
+    case M_PROPERTY_SET:
+        if (!arg)
+            return M_PROPERTY_ERROR;
+        M_PROPERTY_CLAMP(prop, *(int *) arg);
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_SET_MODE, arg);
+	break;
+    case M_PROPERTY_GET:
+        if (!arg)
+            return M_PROPERTY_ERROR;
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_GET_MODE, arg);
+	break;
+    case M_PROPERTY_STEP_UP:
+    case M_PROPERTY_STEP_DOWN:    
+        val = (arg ? *(int *) arg : 1) * (action == M_PROPERTY_STEP_DOWN ? -1 : 1);
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_STEP_MODE, &val);
+	break;
+    default:
+        return M_PROPERTY_NOT_IMPLEMENTED;
+    }
+    return (result==TVI_CONTROL_TRUE?M_PROPERTY_OK:M_PROPERTY_ERROR);
+}
+/// VBI teletext format (R)
+static int mp_property_teletext_format(m_option_t * prop, int action, void *arg,
+                  MPContext * mpctx)
+{
+    int val,result;
+    tvi_handle_t *tvh = mpctx->demuxer->priv;
+    if (mpctx->demuxer->type != DEMUXER_TYPE_TV || !tvh || !tvh->priv_vbi)
+        return M_PROPERTY_UNAVAILABLE;
+
+    switch (action) {
+    case M_PROPERTY_GET:
+        if (!arg)
+            return M_PROPERTY_ERROR;
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_GET_FORMAT, arg);
+	break;
+    case M_PROPERTY_SET:
+        if (!arg)
+            return M_PROPERTY_ERROR;
+        M_PROPERTY_CLAMP(prop, *(int *) arg);
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_SET_FORMAT, arg);
+	break;
+    case M_PROPERTY_STEP_UP:
+    case M_PROPERTY_STEP_DOWN:
+        val = (arg ? *(int *) arg : 1) * (action == M_PROPERTY_STEP_DOWN ? -1 : 1);
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_STEP_FORMAT, &val);
+	break;
+    default:
+        return M_PROPERTY_NOT_IMPLEMENTED;
+    }
+    return (result==TVI_CONTROL_TRUE?M_PROPERTY_OK:M_PROPERTY_ERROR);
+}
+
+/// VBI teletext half-page mode (RW)
+static int mp_property_teletext_half_page(m_option_t * prop, int action, void *arg,
+                  MPContext * mpctx)
+{
+    int val,result;
+    tvi_handle_t *tvh = mpctx->demuxer->priv;
+    if (mpctx->demuxer->type != DEMUXER_TYPE_TV || !tvh || !tvh->priv_vbi)
+        return M_PROPERTY_UNAVAILABLE;
+
+    switch (action) {
+    case M_PROPERTY_GET:
+        if (!arg)
+            return M_PROPERTY_ERROR;
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_GET_HALF_PAGE, arg);
+	break;
+    case M_PROPERTY_SET:
+        if (!arg)
+            return M_PROPERTY_ERROR;
+        M_PROPERTY_CLAMP(prop, *(int *) arg);
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_SET_HALF_PAGE, arg);
+	break;
+    case M_PROPERTY_STEP_UP:
+    case M_PROPERTY_STEP_DOWN:
+        val = (arg ? *(int *) arg : 1) * (action == M_PROPERTY_STEP_DOWN ? -1 : 1);
+        result=tv_teletext_control(tvh, TVI_CONTROL_VBI_STEP_HALF_PAGE, &val);
+	break;
+    default:
+        return M_PROPERTY_NOT_IMPLEMENTED;
+    }
+    return (result==TVI_CONTROL_TRUE?M_PROPERTY_OK:M_PROPERTY_ERROR);
+}
+#endif /* HAVE_TV_TELETEXT */
+
 ///@}
 
 /// All properties available in MPlayer.
@@ -1540,6 +1668,17 @@
      M_OPT_RANGE, -100, 100, (void *) TV_COLOR_HUE },
 #endif
 
+#ifdef HAVE_TV_TELETEXT
+    { "teletext_page", mp_property_teletext_page, CONF_TYPE_INT,
+     M_OPT_RANGE, -999, 999, NULL },
+    { "teletext_mode", mp_property_teletext_mode, CONF_TYPE_INT,
+     M_OPT_RANGE, 0, 3, NULL },
+    { "teletext_format", mp_property_teletext_format, CONF_TYPE_INT,
+     M_OPT_RANGE, 0, 3, NULL },
+    { "teletext_half_page", mp_property_teletext_half_page, CONF_TYPE_INT,
+     M_OPT_RANGE, 0, 2, NULL },
+#endif
+
     { NULL, NULL, NULL, 0, 0, 0, NULL }
 };
 
@@ -2233,6 +2372,16 @@
 	    if (mpctx->file_format == DEMUXER_TYPE_TV)
 		tv_step_chanlist((tvi_handle_t *) (mpctx->demuxer->priv));
 	    break;
+#ifdef HAVE_TV_TELETEXT
+        case MP_CMD_TV_TELETEXT_ADD_DEC:
+            if (mpctx->file_format == DEMUXER_TYPE_TV)
+                tv_teletext_add_dec((tvi_handle_t *) (mpctx->demuxer->priv),cmd->args[0].v.s);
+            break;
+        case MP_CMD_TV_TELETEXT_GO_LINK:
+            if (mpctx->file_format == DEMUXER_TYPE_TV)
+                tv_teletext_go_link((tvi_handle_t *) (mpctx->demuxer->priv),cmd->args[0].v.i);
+            break;
+#endif /* HAVE_TV_TELETEXT */
 #endif				/* USE_TV */
 
 	case MP_CMD_SUB_LOAD:
--- a/configure	Sat Jun 09 18:20:21 2007 +0000
+++ b/configure	Sun Jun 10 00:06:12 2007 +0000
@@ -240,6 +240,7 @@
   --disable-tv-v4l1      disable Video4Linux TV interface [autodetect]
   --disable-tv-v4l2      disable Video4Linux2 TV interface [autodetect]
   --disable-tv-bsdbt848  disable BSD BT848 interface [autodetect]
+  --disable-tv-teletex   disable TV teletext interface [autodetect]
   --disable-pvr          disable Video4Linux2 MPEG PVR [autodetect]
   --disable-rtc          disable RTC (/dev/rtc) on Linux [autodetect]
   --disable-network      disable networking [enable]
@@ -588,6 +589,7 @@
 _tv_v4l1=auto
 _tv_v4l2=auto
 _tv_bsdbt848=auto
+_tv_teletext=auto
 _pvr=auto
 _network=yes
 _winsock2=auto
@@ -935,6 +937,8 @@
   --disable-tv-v4l1)	_tv_v4l1=no	;;
   --enable-tv-v4l2)	_tv_v4l2=yes	;;
   --disable-tv-v4l2)	_tv_v4l2=no	;;
+  --enable-tv-teletext)    _tv_teletext=yes        ;;
+  --disable-tv-teletext)    _tv_teletext=no        ;;
   --enable-radio)       _radio=yes	;;
   --enable-radio-capture)       _radio_capture=yes	;;
   --disable-radio-capture)       _radio_capture=no	;;
@@ -6685,6 +6689,28 @@
 fi
 echores "$_tv_v4l2"
 
+echocheck "TV teletext interface"
+if test "$_tv_teletext" = auto ; then
+ _tv_teletext=no
+ if test linux ; then
+  cat > $TMPC <<EOF
+#include <stdlib.h>
+#include <libzvbi.h>
+int main(void) { return 0; }
+EOF
+  cc_check && _tv_teletext=yes
+ fi
+fi
+if test "$_tv_teletext" = yes ; then
+  _def_tv_teletext='#define HAVE_TV_TELETEXT 1'
+  _ld_extra="$_ld_extra -lzvbi"
+  _inputmodules="tv-teletext $_inputmodules"
+else
+  _noinputmodules="tv-teletext $_noinputmodules"
+  _def_tv_teletext='#undef HAVE_TV_TELETEXT'
+fi
+echores "$_tv_teletext"
+
 
 echocheck "Radio interface"
 if test "$_radio" = yes ; then
@@ -7546,6 +7572,7 @@
 TV_V4L1 = $_tv_v4l1
 TV_V4L2 = $_tv_v4l2
 TV_BSDBT848 = $_tv_bsdbt848
+TV_TELETEXT = $_tv_teletext
 AUDIO_INPUT = $_audio_input
 PVR = $_pvr
 VCD = $_vcd
@@ -8098,6 +8125,9 @@
 /* Enable *BSD BrookTree TV interface support */
 $_def_tv_bsdbt848
 
+/* Enable TV Teletext Interface support */
+$_def_tv_teletext
+
 /* Enable Radio Interface support */
 $_def_radio
 
--- a/input/input.c	Sat Jun 09 18:20:21 2007 +0000
+++ b/input/input.c	Sun Jun 10 00:06:12 2007 +0000
@@ -136,6 +136,10 @@
   { MP_CMD_LOADLIST, "loadlist", 1, { {MP_CMD_ARG_STRING, {0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}} } },
   { MP_CMD_RUN, "run", 1, { {MP_CMD_ARG_STRING,{0}}, {-1,{0}} } },
   { MP_CMD_VF_CHANGE_RECTANGLE, "change_rectangle", 2, { {MP_CMD_ARG_INT,{0}}, {MP_CMD_ARG_INT,{0}}, {-1,{0}}}},
+#ifdef HAVE_TV_TELETEXT
+  { MP_CMD_TV_TELETEXT_ADD_DEC, "teletext_add_dec", 1, { {MP_CMD_ARG_STRING,{0}}, {-1,{0}} } },
+  { MP_CMD_TV_TELETEXT_GO_LINK, "teletext_go_link",1, { { MP_CMD_ARG_INT ,{0}}, { MP_CMD_ARG_INT ,{0}}, {-1,{0}} } },
+#endif
 
 #ifdef HAVE_NEW_GUI  
   { MP_CMD_GUI_LOADFILE, "gui_loadfile", 0, { {-1,{0}} } },
@@ -386,6 +390,12 @@
   { { 'n', 0 }, "tv_step_norm" },
   { { 'u', 0 }, "tv_step_chanlist" },
 #endif
+#ifdef HAVE_TV_TELETEXT
+  { { 'X', 0 }, "step_property teletext_mode 1" },
+  { { 'E', 0 }, "step_property teletext_half_page 1" },
+  { { 'W', 0 }, "step_property teletext_page 1" },
+  { { 'Q', 0 }, "step_property teletext_page -1" },
+#endif
 #ifdef HAVE_JOYSTICK
   { { JOY_AXIS0_PLUS, 0 }, "seek 10" },
   { { JOY_AXIS0_MINUS, 0 }, "seek -10" },
--- a/input/input.h	Sat Jun 09 18:20:21 2007 +0000
+++ b/input/input.h	Sun Jun 10 00:06:12 2007 +0000
@@ -93,6 +93,8 @@
 #define MP_CMD_STEP_PROPERTY 91
 #define MP_CMD_RADIO_STEP_FREQ 92
 #define MP_CMD_TV_STEP_FREQ 93
+#define MP_CMD_TV_TELETEXT_ADD_DEC 94
+#define MP_CMD_TV_TELETEXT_GO_LINK 95
 
 #define MP_CMD_GUI_EVENTS       5000
 #define MP_CMD_GUI_LOADFILE     5001
--- a/libvo/sub.c	Sat Jun 09 18:20:21 2007 +0000
+++ b/libvo/sub.c	Sun Jun 10 00:06:12 2007 +0000
@@ -67,6 +67,10 @@
 font_desc_t* sub_font=NULL;
 
 unsigned char* vo_osd_text=NULL;
+#ifdef HAVE_TV_TELETEXT
+unsigned char* vo_osd_teletex_text=NULL;
+int vo_osd_teletext_flip = 0;
+#endif
 int sub_unicode=0;
 int sub_utf8=0;
 int sub_pos=100;
@@ -229,6 +233,121 @@
 }
 #endif
 
+#ifdef HAVE_TV_TELETEXT
+inline static void vo_update_text_teletext(mp_osd_obj_t *obj, int dxs, int dys)
+{
+    char *p,*pe;
+    char line[256];
+    int h=0,w=0,i,c,w1,font,lines,endline;
+    int x1,y1,x2,y2;
+    unsigned char *t;
+
+    obj->flags|=OSDFLAG_CHANGED|OSDFLAG_VISIBLE;
+
+    if (vo_osd_teletex_text==NULL) {
+        obj->flags&=~OSDFLAG_VISIBLE;
+        return;
+    }
+    p=vo_osd_teletex_text;
+    lines=0;
+    endline=0;
+    do {    // calculate teletext size
+        memset(line,0,sizeof(line));
+        if(pe=strchr(p,'\n')) {
+            if(pe-p>sizeof(line))
+                strncpy(line,p,sizeof(line));
+            else
+                strncpy(line,p,pe-p);
+        }
+        else
+            strncpy(line,p,sizeof(line));
+
+        t=line;
+        w1=0;
+        while (*t) {
+            c = utf8_get_char(&t);
+            if (!c) c++; // avoid UCS 0
+            render_one_glyph(vo_font, c);
+            w1+=vo_font->width[c]+vo_font->charspace;
+        }
+        h+=vo_font->height;
+        if(w1>w) w=w1;
+        if(pe) pe++;
+        p=pe;
+        lines++;
+        if(h+vo_font->height*2>dys && endline==0) endline=lines;
+    } while (pe!=NULL);
+    h=h+vo_font->height;
+    w=w-vo_font->charspace;
+    if (w>dxs){
+        // calculate bbox size
+        x1=0; 
+        x2=dxs;
+    } 
+    else 
+    {
+        x1=(dxs-w)/2; 
+        x2=x1+w+1;
+    }
+    if (h>dys){
+        y1=0;
+        y2=dys;
+    }
+    else {
+        y1=0;
+        y2=y1+h+1;
+    }
+    obj->bbox.x1 = obj->x = x1;
+    obj->bbox.y1 = obj->y = y1;
+    obj->bbox.x2 = x2;
+    obj->bbox.y2 = y2;
+    obj->flags |= OSDFLAG_BBOX;
+    alloc_buf(obj);
+    p=vo_osd_teletex_text;
+    h=y1;
+    if (vo_osd_teletext_flip)
+        endline=lines-endline;    // bottom page
+    else
+        endline=0;                // top page
+    lines=0;
+    do {    // show teletext page
+        memset(line,0,sizeof(line));
+        if(pe=strchr(p,'\n')) {
+            if(pe-p>sizeof(line))
+                strncpy(line,p,sizeof(line));
+            else
+                strncpy(line,p,pe-p);}
+        else
+            strncpy(line,p,sizeof(line));
+
+        t=line;
+
+        w1=x1;
+        if(lines==0 || endline==0 || lines>endline) {
+            while (*t) {
+                c = utf8_get_char(&t);
+                if (!c) c++; // avoid UCS 0
+                render_one_glyph(vo_font, c);
+                if(w1+vo_font->width[c]>=x2) break;
+                    if ((font=vo_font->font[c])>=0)
+                        draw_alpha_buf(obj,w1,h,
+                            vo_font->width[c],
+                            vo_font->pic_a[font]->h,
+                            vo_font->pic_b[font]->bmp+vo_font->start[c],
+                            vo_font->pic_a[font]->bmp+vo_font->start[c],
+                            vo_font->pic_a[font]->w);
+                    w1+=vo_font->width[c]+vo_font->charspace;
+            }
+            h+=vo_font->height;
+        }
+        if(pe) pe++;
+        p=pe;
+        if(h+vo_font->height*2>dys) pe=NULL;
+        lines++;
+    } while (pe!=NULL);
+}
+#endif
+
 int vo_osd_progbar_type=-1;
 int vo_osd_progbar_value=100;   // 0..256
 
@@ -859,6 +978,11 @@
 	case OSDTYPE_SUBTITLE:
 	    vo_update_text_sub(obj,dxs,dys);
 	    break;
+#ifdef HAVE_TV_TELETEXT
+        case OSDTYPE_TELETEXT:
+            vo_update_text_teletext(obj,dxs,dys);
+            break;
+#endif
 	case OSDTYPE_PROGBAR:
 	    vo_update_text_progbar(obj,dxs,dys);
 	    break;
@@ -926,6 +1050,9 @@
 #ifdef USE_DVDNAV
     new_osd_obj(OSDTYPE_DVDNAV);
 #endif
+#if HAVE_TV_TELETEXT
+    new_osd_obj(OSDTYPE_TELETEXT);
+#endif
 #ifdef HAVE_FREETYPE
     force_load_font = 1;
 #endif
@@ -964,6 +1091,9 @@
 #ifdef USE_DVDNAV
         case OSDTYPE_DVDNAV:
 #endif
+#ifdef HAVE_TV_TELETEXT
+        case OSDTYPE_TELETEXT:
+#endif
 	case OSDTYPE_OSD:
 	case OSDTYPE_SUBTITLE:
 	case OSDTYPE_PROGBAR:
--- a/libvo/sub.h	Sat Jun 09 18:20:21 2007 +0000
+++ b/libvo/sub.h	Sun Jun 10 00:06:12 2007 +0000
@@ -2,6 +2,10 @@
 #ifndef __MPLAYER_SUB_H
 #define __MPLAYER_SUB_H
 
+#ifdef HAVE_TV_TELETEXT
+#include "libmpcodecs/mp_image.h"
+#endif
+
 typedef struct mp_osd_bbox_s {
     int x1,y1,x2,y2;
 } mp_osd_bbox_t;
@@ -11,6 +15,7 @@
 #define OSDTYPE_PROGBAR 3
 #define OSDTYPE_SPU 4
 #define OSDTYPE_DVDNAV 5
+#define OSDTYPE_TELETEXT 6
 
 #define OSDFLAG_VISIBLE 1
 #define OSDFLAG_CHANGED 2
@@ -64,6 +69,11 @@
 
 extern unsigned char* vo_osd_text;
 
+#ifdef HAVE_TV_TELETEXT
+extern unsigned char* vo_osd_teletex_text;
+extern int vo_osd_teletext_flip;
+#endif
+
 extern int vo_osd_progbar_type;
 extern int vo_osd_progbar_value;   // 0..255
 
--- a/mpcommon.c	Sat Jun 09 18:20:21 2007 +0000
+++ b/mpcommon.c	Sun Jun 10 00:06:12 2007 +0000
@@ -7,6 +7,9 @@
 #include "libvo/video_out.h"
 #include "spudec.h"
 #include "vobsub.h"
+#ifdef HAVE_TV_TELETEXT
+#include "stream/tv.h"
+#endif
 
 double sub_last_pts = -303;
 
@@ -138,3 +141,31 @@
     }
     current_module=NULL;
 }
+
+void update_teletext(sh_video_t *sh_video, demuxer_t *demuxer, int reset)
+{
+#ifdef HAVE_TV_TELETEXT
+    int half_page;
+    tvi_handle_t *tvh = demuxer->priv;
+    if (demuxer->type != DEMUXER_TYPE_TV) return;
+    if(!tvh) return;
+    if(vo_spudec) {
+        tv_teletext_img_t* img=tv_get_teletext_imgpage(tvh);
+        if(img!=NULL) {
+            spudec_heartbeat_teletext(vo_spudec, img);
+            if(img->canvas) 
+                free(img->canvas);
+            free(img);
+            vo_osd_changed(OSDTYPE_SPU);
+            vo_osd_teletex_text=NULL;
+            vo_osd_changed(OSDTYPE_TELETEXT);
+            return;
+        }
+        vo_osd_changed(OSDTYPE_SPU);
+    }
+    vo_osd_teletex_text=tv_get_teletext_txtpage(tvh);
+    tv_teletext_control(tvh,TVI_CONTROL_VBI_GET_HALF_PAGE,&half_page);
+    vo_osd_teletext_flip=half_page;
+    vo_osd_changed(OSDTYPE_TELETEXT);
+#endif
+}
--- a/mplayer.c	Sat Jun 09 18:20:21 2007 +0000
+++ b/mplayer.c	Sun Jun 10 00:06:12 2007 +0000
@@ -1040,6 +1040,10 @@
     spudec_set_font_factor(vo_spudec,font_factor);
   }
 
+#ifdef HAVE_TV_TELETEXT
+    if (vo_spudec==NULL && mpctx->demuxer->type==DEMUXER_TYPE_TV)
+        vo_spudec=spudec_new_scaled(NULL, mpctx->sh_video->disp_w, mpctx->sh_video->disp_h);
+#endif
   if (vo_spudec!=NULL)
     inited_flags|=INITED_SPUDEC;
 }
@@ -1622,6 +1626,7 @@
 	decoded_frame = decode_video(sh_video, start, in_size, 0, pts);
 	if (decoded_frame) {
 	    update_subtitles(sh_video, mpctx->d_sub, 0);
+	    update_teletext(sh_video, mpctx->demuxer, 0);
 	    update_osd_msg();
 	    current_module = "filter video";
 	    if (filter_video(sh_video, decoded_frame, sh_video->pts))
@@ -2036,6 +2041,7 @@
 	    ++total_frame_cnt;
 	}
 	update_subtitles(sh_video, mpctx->d_sub, 0);
+	update_teletext(sh_video, mpctx->demuxer, 0);
 	update_osd_msg();
 	current_module = "decode_video";
 	decoded_frame = decode_video(sh_video, start, in_size, drop_frame,
@@ -2249,6 +2255,7 @@
 	// be completely wrong (probably 0).
 	mpctx->sh_video->pts = mpctx->d_video->pts;
 	update_subtitles(mpctx->sh_video, mpctx->d_sub, 1);
+	update_teletext(mpctx->sh_video, mpctx->demuxer, 1);
     }
       
     if (mpctx->sh_audio) {
@@ -3123,7 +3130,11 @@
 
 //================== Read SUBTITLES (DVD & TEXT) ==========================
 if(vo_spudec==NULL && mpctx->sh_video &&
-     (mpctx->stream->type==STREAMTYPE_DVD || mpctx->stream->type == STREAMTYPE_DVDNAV || mpctx->d_sub->id >= 0)){
+     (mpctx->stream->type==STREAMTYPE_DVD || mpctx->stream->type == STREAMTYPE_DVDNAV ||
+#ifdef HAVE_TV_TELETEXT
+     mpctx->demuxer->type==DEMUXER_TYPE_TV ||
+#endif
+     mpctx->d_sub->id >= 0)){
   init_vo_spudec();
 }
 
--- a/spudec.c	Sat Jun 09 18:20:21 2007 +0000
+++ b/spudec.c	Sun Jun 10 00:06:12 2007 +0000
@@ -29,6 +29,9 @@
 #include "avutil.h"
 #endif
 #include "libswscale/swscale.h"
+#ifdef HAVE_TV_TELETEXT
+#include "stream/tv.h"
+#endif
 
 /* Valid values for spu_aamode:
    0: none (fastest, most ugly)
@@ -1185,3 +1188,115 @@
   spu->hw_spu = hw_spu;
   hw_spu->control(VOCTRL_SET_SPU_PALETTE,spu->global_palette);
 }
+
+#ifdef HAVE_TV_TELETEXT
+#define VBI_R(rgba) (((rgba) >> 0) & 0xFF)
+#define VBI_G(rgba) (((rgba) >> 8) & 0xFF)
+#define VBI_B(rgba) (((rgba) >> 16) & 0xFF)
+#define VBI_A(rgba) (((rgba) >> 24) & 0xFF)
+
+
+static unsigned char rgbtoy(int r, int g, int b) {
+    int ret=(257*r+504*g+98*b+16000)/1000;
+    return ret & 0xff;
+}
+
+/// correction u and v planes half size
+#define SPU_DOUBLE_SIZE    1
+
+void alloc_images(spudec_handle_t* spu, int cmode) {
+
+    if (spu->image_size < spu->stride * spu->height) {
+        if (spu->image != NULL) {
+            free(spu->image);
+            spu->image_size = 0;
+        }
+        spu->image = malloc(2 * spu->stride * spu->height);
+        if (spu->image) {
+            spu->image_size = spu->stride * spu->height;
+            spu->aimage = spu->image + spu->image_size;
+        }
+    }
+}
+
+/**
+    Render from VBI_PIXFMT_RGBA32_LE to spu
+**/
+void spudec_heartbeat_teletext(void *this, void *imgptr)
+{
+    int px,py;
+    int grey,alpha,cy,cu,cv,alphauv;
+    uint32_t *canvas;
+    uint32_t *pin;
+    spudec_handle_t *spu = (spudec_handle_t*)this;
+    tv_teletext_img_t *img = (tv_teletext_img_t*)imgptr;
+    unsigned char *iptr;
+    unsigned char *aptr;
+    int h1 = 10;
+    int hs = 0;
+
+    if(!spu || !img)
+        return;
+    if(img->canvas==NULL) {
+        spudec_reset(spu);
+        if (spu->image)
+            free(spu->image);
+        spu->image=NULL;
+        spu->image_size = 0;
+        return;
+    }
+
+    if(img->half) h1=5;                // top half page
+    if(img->half==2) hs=5;             // bottom half page
+
+    spu->start_pts=0;
+    spu->end_pts=0;
+    spu->now_pts=1;
+    spu->orig_frame_width = img->columns*12;    // 1 char width 12 pixel
+    spu->orig_frame_height = img->rows*h1;      // 1 char height 10 pixel
+    spu->scaled_frame_width = 0;
+    spu->scaled_frame_height = 0;
+    spu->start_col = 0;
+    spu->end_col = img->columns*12;
+    spu->start_row = 0;
+    spu->end_row = img->rows*h1;
+    spu->height = img->rows*h1;
+    spu->width = img->columns*12;
+    spu->height = (spu->height+3)&(~3);         // round to 4
+    spu->stride = (spu->width+7)&(~7);          // round to 8
+
+    alloc_images(spu,img->tformat);             // alloc images buffer
+    if (spu->image == NULL) {
+        spudec_reset(spu);
+        return;
+    }
+    canvas=img->canvas;                         // RGBA32_LE image
+    pin=canvas+(hs*img->columns*12*img->rows);
+    memset(spu->image,0,spu->image_size*2);
+
+    for(py=0;py<img->rows*h1;py++) {
+        iptr=spu->image+(py-hs)*spu->stride;    // image ptr
+        aptr=spu->aimage+(py-hs)*spu->stride;   // alpha ptr
+        for(px=0;px<img->columns*12;px++) {
+            grey=rgbtoy(VBI_R(*pin),VBI_G(*pin),VBI_B(*pin));    // RGB to Y
+            if(grey<=0x10) grey=0;
+            alpha=VBI_A(*pin);
+            switch (img->tformat) {
+                case 0x01:    // BW
+                case 0x02:    // Gray
+                case 0x03:    // Color (not supported)
+                    alpha=0x100-alpha;
+                    if (grey + alpha > 255) grey = 256 - alpha;
+                    break;
+            }
+            *iptr=grey;    // store Y plane
+            *aptr=alpha;   // store alpha
+            iptr++;
+            aptr++;
+            pin++;
+        }
+    }
+    spu->start_pts=0;
+    spu->end_pts=UINT_MAX;
+}
+#endif
--- a/spudec.h	Sat Jun 09 18:20:21 2007 +0000
+++ b/spudec.h	Sun Jun 10 00:06:12 2007 +0000
@@ -20,5 +20,8 @@
 void spudec_calc_bbox(void *me, unsigned int dxs, unsigned int dys, unsigned int* bbox);
 void spudec_draw_scaled(void *me, unsigned int dxs, unsigned int dys, void (*draw_alpha)(int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride));
 void spudec_set_forced_subs_only(void * const this, const unsigned int flag);
+#ifdef HAVE_TV_TELETEXT
+void spudec_heartbeat_teletext(void *this, void *imgptr);
+#endif
 #endif
 
--- a/stream/Makefile	Sat Jun 09 18:20:21 2007 +0000
+++ b/stream/Makefile	Sun Jun 10 00:06:12 2007 +0000
@@ -51,6 +51,7 @@
 SRCS_COMMON-$(TV_BSDBT848)       += tvi_bsdbt848.c
 SRCS_COMMON-$(TV_V4L1)           += tvi_v4l.c  audio_in.c
 SRCS_COMMON-$(TV_V4L2)           += tvi_v4l2.c audio_in.c
+SRCS_COMMON-$(TV_TELETEXT)       += tvi_vbi.c
 SRCS_COMMON-$(VCD)               += stream_vcd.c
 SRCS_COMMON-$(VSTREAM)           += stream_vstream.c
 
--- a/stream/tv.c	Sat Jun 09 18:20:21 2007 +0000
+++ b/stream/tv.c	Sun Jun 10 00:06:12 2007 +0000
@@ -34,6 +34,10 @@
 
 #include "frequencies.h"
 
+#ifdef HAVE_TV_TELETEXT
+#include "tvi_vbi.h"
+#endif
+
 /* some default values */
 int tv_param_audiorate = 44100;
 int tv_param_noaudio = 0;
@@ -518,6 +522,9 @@
 	tv_uninit(tvh);
 	return NULL;
     }
+#ifdef HAVE_TV_TELETEXT
+    if(tvh->priv_vbi) teletext_control(tvh->priv_vbi,TVI_CONTROL_VBI_START,1); //arg must be not null
+#endif
     funcs = tvh->functions;
     demuxer->priv=tvh;
     
@@ -657,6 +664,12 @@
 static void demux_close_tv(demuxer_t *demuxer)
 {
     tvi_handle_t *tvh=(tvi_handle_t*)(demuxer->priv);
+#ifdef HAVE_TV_TELETEXT
+    if(tvh->priv_vbi) {
+        teletext_uninit(tvh->priv_vbi);
+        tvh->priv_vbi=NULL;
+    }
+#endif
     if (!tvh) return;
     tvh->functions->uninit(tvh->priv);
     demuxer->priv=NULL;
@@ -688,6 +701,10 @@
             tvi_driver_list[i]->name,
             tvi_driver_list[i]->author,
             tvi_driver_list[i]->comment?tvi_driver_list[i]->comment:"");
+#ifdef HAVE_TV_TELETEXT
+            h->priv_vbi=teletext_init();
+#endif
+
             return h;
         }
     }
@@ -773,6 +790,9 @@
 	mp_msg(MSGT_TV, MSGL_V, MSGTR_TV_CurrentFrequency,
 	    freq, (float)freq/16);
     }
+#ifdef HAVE_TV_TELETEXT
+    if (tvh->priv_vbi) teletext_control(tvh->priv_vbi,TVI_CONTROL_VBI_RESET,1); //arg must be not null
+#endif
     return(1);
 }
 
@@ -932,6 +952,9 @@
       return 0;
     }
   }
+#ifdef HAVE_TV_TELETEXT
+    if (tvh->priv_vbi) teletext_control(tvh->priv_vbi,TVI_CONTROL_VBI_RESET,1); //arg must be not null
+#endif
     return(1);
 }
 
@@ -949,9 +972,61 @@
 	mp_msg(MSGT_TV, MSGL_ERR, MSGTR_TV_CannotSetNorm);
 	return 0;
     }
+#ifdef HAVE_TV_TELETEXT
+    if (tvh->priv_vbi) teletext_control(tvh->priv_vbi,TVI_CONTROL_VBI_RESET,1); //arg must be not null
+#endif
     return(1);
 }
 
+#ifdef HAVE_TV_TELETEXT
+int tv_teletext_control(tvi_handle_t* tvh, int control, void* arg)
+{
+    if (!tvh || !tvh->priv_vbi)
+        return TVI_CONTROL_FALSE;
+
+    return teletext_control(tvh->priv_vbi,control,arg);
+}
+
+int tv_teletext_add_dec(tvi_handle_t *tvh, char *dec)
+{
+    if(!dec) return 0;
+    if (teletext_control(tvh->priv_vbi, TVI_CONTROL_VBI_ADD_DEC, &dec)!= TVI_CONTROL_TRUE)
+        return 0;
+    return 1;
+}
+
+int tv_teletext_go_link(tvi_handle_t *tvh, int linkno)
+{
+    if (teletext_control(tvh->priv_vbi, TVI_CONTROL_VBI_GO_LINK, &linkno)!= TVI_CONTROL_TRUE)
+        return 0;
+    return 1;
+}
+
+void* tv_get_teletext_vbipage(tvi_handle_t *tvh)
+{
+    void* page = NULL;
+    if (teletext_control(tvh->priv_vbi, TVI_CONTROL_VBI_GET_VBIPAGE, &page)!= TVI_CONTROL_TRUE)
+        return NULL;
+    return page;
+}
+
+char* tv_get_teletext_txtpage(tvi_handle_t *tvh)
+{
+    char* page = NULL;
+    if (teletext_control(tvh->priv_vbi, TVI_CONTROL_VBI_GET_TXTPAGE, &page)!= TVI_CONTROL_TRUE)
+        return NULL;
+    return page;
+}
+
+tv_teletext_img_t* tv_get_teletext_imgpage(tvi_handle_t *tvh)
+{
+    tv_teletext_img_t* tv_teletext_img = NULL;
+    if (teletext_control(tvh->priv_vbi, TVI_CONTROL_VBI_GET_IMGPAGE, &tv_teletext_img)!= TVI_CONTROL_TRUE)
+        return NULL;
+    return tv_teletext_img;
+}
+#endif
+
 demuxer_desc_t demuxer_desc_tv = {
   "Tv card demuxer",
   "tv",
--- a/stream/tv.h	Sat Jun 09 18:20:21 2007 +0000
+++ b/stream/tv.h	Sun Jun 10 00:06:12 2007 +0000
@@ -43,6 +43,11 @@
 #endif
 extern char* tv_param_adevice;
 #endif
+#ifdef HAVE_TV_TELETEXT
+extern char* tv_param_tdevice;        ///< teletext vbi device
+extern char* tv_param_tformat;        ///< format: text,bw,gray,color
+extern int tv_param_tpage;            ///< page number
+#endif
 extern int tv_param_brightness;
 extern int tv_param_contrast;
 extern int tv_param_hue;
@@ -72,6 +77,9 @@
 typedef struct tvi_handle_s {
     tvi_functions_t	*functions;
     void		*priv;
+#ifdef HAVE_TV_TELETEXT
+    void                *priv_vbi;
+#endif
     int 		seq;
 
     /* specific */
@@ -90,6 +98,18 @@
     struct tv_channels_s *prev;
 } tv_channels_t;
 
+#ifdef HAVE_TV_TELETEXT
+typedef struct tv_teletext_img_s {
+    void*    canvas;
+    int        tformat;
+    int        columns;
+    int        rows;
+    int        height;
+    int        width;
+    int        half;
+} tv_teletext_img_t;
+#endif
+
 extern tv_channels_t *tv_channel_list;
 extern tv_channels_t *tv_channel_current, *tv_channel_last;
 extern char *tv_channel_last_real;
@@ -153,6 +173,31 @@
 #define TVI_CONTROL_SPC_SET_INPUT	0x402	/* set input channel (tv,s-video,composite..) */
 #define TVI_CONTROL_SPC_GET_NORMID	0x403	/* get normid from norm name */
 
+/* TELETEXT controls */
+#define TVI_CONTROL_VBI_SET_MODE        0x501   ///< on/off grab teletext
+#define TVI_CONTROL_VBI_GET_MODE        0x502   ///< get current mode teletext
+#define TVI_CONTROL_VBI_STEP_MODE       0x503  ///< step teletext mode
+
+#define TVI_CONTROL_VBI_SET_PAGE        0x504   ///< set grab teletext page number
+#define TVI_CONTROL_VBI_STEP_PAGE       0x505   ///< step grab teletext page number
+#define TVI_CONTROL_VBI_GET_PAGE        0x506   ///< get grabbed teletext page
+
+#define TVI_CONTROL_VBI_SET_FORMAT      0x507   ///< set teletext format
+#define TVI_CONTROL_VBI_STEP_FORMAT     0x508   ///< step teletext format
+#define TVI_CONTROL_VBI_GET_FORMAT      0x509   ///< get eletext format
+
+#define TVI_CONTROL_VBI_GET_HALF_PAGE   0x50a   ///< get current half page
+#define TVI_CONTROL_VBI_STEP_HALF_PAGE  0x50b   ///< switch half page
+#define TVI_CONTROL_VBI_SET_HALF_PAGE   0x50c   ///< switch half page
+
+#define TVI_CONTROL_VBI_ADD_DEC         0x50d   ///< add page number with dec
+#define TVI_CONTROL_VBI_GO_LINK         0x50e   ///< go link (1..6)
+#define TVI_CONTROL_VBI_GET_TXTPAGE     0x50f   ///< get grabbed text teletext page
+#define TVI_CONTROL_VBI_GET_IMGPAGE     0x510   ///< get grabbed image teletext page
+#define TVI_CONTROL_VBI_GET_VBIPAGE     0x511   ///< get vbi_image for grabbed teletext page
+#define TVI_CONTROL_VBI_RESET           0x512   ///< vbi reset
+#define TVI_CONTROL_VBI_START           0x513   ///< vbi start
+
 extern tvi_handle_t *tv_begin(void);
 extern int tv_init(tvi_handle_t *tvh);
 extern int tv_uninit(tvi_handle_t *tvh);
@@ -183,6 +228,20 @@
 
 int tv_set_norm(tvi_handle_t *tvh, char* norm);
 
+#ifdef HAVE_TV_TELETEXT
+int tv_teletext_control(tvi_handle_t* tvh, int control,void* arg);
+/// add dec to pageno
+int tv_teletext_add_dec(tvi_handle_t *tvh, char *dec);
+/// go link
+int tv_teletext_go_link(tvi_handle_t *tvh, int linkno);
+/// get current vbi_page
+void* tv_get_teletext_vbipage(tvi_handle_t *tvh);
+/// get current page text
+char* tv_get_teletext_txtpage(tvi_handle_t *tvh);
+/// get current page image (RGB32_LB format)
+tv_teletext_img_t* tv_get_teletext_imgpage(tvi_handle_t *tvh);
+#endif
+
 #define TV_NORM_PAL		1
 #define TV_NORM_NTSC		2
 #define TV_NORM_SECAM		3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stream/tvi_vbi.c	Sun Jun 10 00:06:12 2007 +0000
@@ -0,0 +1,1197 @@
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "tv.h"
+#include "tvi_vbi.h"
+#include "mp_msg.h"
+#include "libmpcodecs/img_format.h"
+
+#ifdef USE_ICONV
+#include <iconv.h>
+#endif
+
+#define VBI_TEXT_CHARSET    "UTF-8"
+
+char* tv_param_tdevice=NULL;        ///< teletext vbi device
+char* tv_param_tformat="gray";      ///< format: text,bw,gray,color
+int tv_param_tpage=100;             ///< page number
+
+
+#ifdef USE_ICONV
+/*
+------------------------------------------------------------------
+    zvbi-0.2.25/src/exp-txt.c skip debug "if(1) fprintf(stderr,) " message
+------------------------------------------------------------------
+*/
+
+/**
+ *  libzvbi - Text export functions
+ *
+ *  Copyright (C) 2001, 2002 Michael H. Schimek
+ *
+ *  Based on code from AleVT 1.5.1
+ *  Copyright (C) 1998, 1999 Edgar Toernig <froese@gmx.de>
+ *
+ *  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 of the License, 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; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ **/
+
+/** $Id$ **/
+
+static vbi_bool
+print_unicode(iconv_t cd, int endian, int unicode, char **p, int n)
+{
+    char in[2], *ip, *op;
+    size_t li, lo, r;
+
+    in[0 + endian] = unicode;
+    in[1 - endian] = unicode >> 8;
+    ip = in; op = *p;
+    li = sizeof(in); lo = n;
+
+    r = iconv(cd, &ip, &li, &op, &lo);
+
+    if ((size_t) -1 == r
+        || (**p == 0x40 && unicode != 0x0040)) {
+        in[0 + endian] = 0x20;
+        in[1 - endian] = 0;
+        ip = in; op = *p;
+        li = sizeof(in); lo = n;
+
+        r = iconv(cd, &ip, &li, &op, &lo);
+
+        if ((size_t) -1 == r
+            || (r == 1 && **p == 0x40))
+            goto error;
+    }
+
+    *p = op;
+
+    return TRUE;
+
+  error:
+    return FALSE;
+}
+
+static int
+vbi_print_page_region_nodebug(vbi_page * pg, char *buf, int size,
+                              const char *format, vbi_bool table,
+                              vbi_bool rtl, int column, int row, int width,
+                              int height)
+{
+    int endian = vbi_ucs2be();
+    int column0, column1, row0, row1;
+    int x, y, spaces, doubleh, doubleh0;
+    iconv_t cd;
+    char *p;
+
+    rtl = rtl;
+
+#if 0
+    if (1)
+        fprintf (stderr, "vbi_print_page_region '%s' "
+            "table=%d col=%d row=%d width=%d height=%d\n",
+             format, table, column, row, width, height);
+#endif
+
+    column0 = column;
+    row0 = row;
+    column1 = column + width - 1;
+    row1 = row + height - 1;
+
+    if (!pg || !buf || size < 0 || !format
+        || column0 < 0 || column1 >= pg->columns
+        || row0 < 0 || row1 >= pg->rows
+        || endian < 0)
+        return 0;
+
+    if ((cd = iconv_open(format, "UCS-2")) == (iconv_t) -1)
+        return 0;
+
+    p = buf;
+
+    doubleh = 0;
+
+    for (y = row0; y <= row1; y++) {
+        int x0, x1, xl;
+
+        x0 = (table || y == row0) ? column0 : 0;
+        x1 = (table || y == row1) ? column1 : (pg->columns - 1);
+
+        xl = (table || y != row0 || (y + 1) != row1) ? -1 : column1;
+
+        doubleh0 = doubleh;
+
+        spaces = 0;
+        doubleh = 0;
+
+        for (x = x0; x <= x1; x++) {
+            vbi_char ac = pg->text[y * pg->columns + x];
+
+            if (table) {
+                if (ac.size > VBI_DOUBLE_SIZE)
+                    ac.unicode = 0x0020;
+            } else {
+                switch (ac.size) {
+                case VBI_NORMAL_SIZE:
+                case VBI_DOUBLE_WIDTH:
+                    break;
+
+                case VBI_DOUBLE_HEIGHT:
+                case VBI_DOUBLE_SIZE:
+                    doubleh++;
+                    break;
+
+                case VBI_OVER_TOP:
+                case VBI_OVER_BOTTOM:
+                    continue;
+
+                case VBI_DOUBLE_HEIGHT2:
+                case VBI_DOUBLE_SIZE2:
+                    if (y > row0)
+                        ac.unicode = 0x0020;
+                    break;
+                }
+
+                /*
+                 *  Special case two lines row0 ... row1, and all chars
+                 *  in row0, column0 ... column1 are double height: Skip
+                 *  row1, don't wrap around.
+                 */
+                if (x == xl && doubleh >= (x - x0)) {
+                    x1 = xl;
+                    y = row1;
+                }
+
+                if (ac.unicode == 0x20 || !vbi_is_print(ac.unicode)) {
+                    spaces++;
+                    continue;
+                } else {
+                    if (spaces < (x - x0) || y == row0) {
+                        for (; spaces > 0; spaces--)
+                            if (!print_unicode(cd, endian, 0x0020,
+                                               &p, buf + size - p))
+                                goto failure;
+                    } else /* discard leading spaces */
+                        spaces = 0;
+                }
+            }
+
+            if (!print_unicode(cd, endian, ac.unicode, &p, buf + size - p))
+                goto failure;
+        }
+
+        /* if !table discard trailing spaces and blank lines */
+
+        if (y < row1) {
+            int left = buf + size - p;
+
+            if (left < 1)
+                goto failure;
+
+            if (table) {
+                *p++ = '\n'; /* XXX convert this (eg utf16) */
+            } else if (spaces >= (x1 - x0)) {
+                ; /* suppress blank line */
+            } else {
+                /* exactly one space between adjacent rows */
+                if (!print_unicode(cd, endian, 0x0020, &p, left))
+                    goto failure;
+            }
+        } else {
+            if (doubleh0 > 0) {
+                ; /* prentend this is a blank double height lower row */
+            } else {
+                for (; spaces > 0; spaces--)
+                    if (!print_unicode(cd, endian, 0x0020, &p, buf + size - p))
+                        goto failure;
+            }
+        }
+    }
+
+    iconv_close(cd);
+    return p - buf;
+
+  failure:
+    iconv_close(cd);
+    return 0;
+}
+#endif
+/*
+ end of zvbi-0.2.25/src/exp-txt.c part
+*/
+
+
+/*
+------------------------------------------------------------------
+  Private routines
+------------------------------------------------------------------
+*/
+
+/**
+ * \brief Decode event handler
+ * \param ev VBI event
+ * \param data pointer to user defined data
+ *
+ */
+static void event_handler(vbi_event * ev, void *data)
+{
+    priv_vbi_t *user_vbi = (priv_vbi_t *) data;
+    vbi_page pg;
+    char *s;
+    int i;
+
+    switch (ev->type) {
+    case VBI_EVENT_CAPTION:
+        mp_msg(MSGT_TV,MSGL_DBG3,"caption\n");
+        break;
+    case VBI_EVENT_NETWORK:
+        s = ev->ev.network.name;
+        if (s) {
+            pthread_mutex_lock(&(user_vbi->buffer_mutex));
+            if (user_vbi->network_name)
+                free(user_vbi->network_name);
+            user_vbi->network_name = strdup(s);
+            pthread_mutex_unlock(&(user_vbi->buffer_mutex));
+        }
+        break;
+    case VBI_EVENT_NETWORK_ID:
+        s = ev->ev.network.name;
+        if (s) {
+            pthread_mutex_lock(&(user_vbi->buffer_mutex));
+            if (user_vbi->network_id)
+                free(user_vbi->network_id);
+            user_vbi->network_id = strdup(s);
+            pthread_mutex_unlock(&(user_vbi->buffer_mutex));
+        }
+        break;
+    case VBI_EVENT_TTX_PAGE:
+        pthread_mutex_lock(&(user_vbi->buffer_mutex));
+        user_vbi->curr_pgno = ev->ev.ttx_page.pgno;     // page number
+        user_vbi->curr_subno = ev->ev.ttx_page.subno;   // subpage
+        i = vbi_bcd2dec(ev->ev.ttx_page.pgno);
+        if (i > 0 && i < 1000) {
+            if (!user_vbi->cache[i])
+                user_vbi->cache[i] = (vbi_page *) malloc(sizeof(vbi_page));
+            vbi_fetch_vt_page(user_vbi->decoder,        // fetch page
+                              user_vbi->cache[i],
+                              ev->ev.ttx_page.pgno,
+                              ev->ev.ttx_page.subno,
+                              VBI_WST_LEVEL_3p5, 25, TRUE);
+            memcpy(user_vbi->theader, user_vbi->cache[i]->text,
+                sizeof(user_vbi->theader));
+        }
+        pthread_mutex_unlock(&(user_vbi->buffer_mutex));
+        break;
+        }
+}
+
+/**
+ * \brief Prepares page to be shown on screen
+ * \param priv_vbi private data structure
+ *
+ * This routine adds page number, current time, etc to page header
+ *
+ */
+static void process_page(priv_vbi_t * priv_vbi)
+{
+    char *pagesptr;
+    int csize, i, j, subtitle = 0, sflg, send;
+    void *canvas;
+    char cpage[5];
+    vbi_page page;
+
+    memcpy(&(page), priv_vbi->page, sizeof(vbi_page));
+    if (priv_vbi->pgno != priv_vbi->page->pgno) {
+        //don't clear first line
+        for (i = page.columns; i < 1056; i++) {
+            page.text[i].unicode = ' ';
+            page.text[i].background = VBI_TRANSPARENT_COLOR;
+        }
+        snprintf(cpage, sizeof(cpage), "%03X", priv_vbi->pgno);
+        page.text[1].unicode = cpage[0];
+        page.text[2].unicode = cpage[1];
+        page.text[3].unicode = cpage[2];
+        page.text[4].unicode = ' ';
+        page.text[5].unicode = ' ';
+        page.text[6].unicode = ' ';
+    }
+
+    //background page number & title
+    j=vbi_bcd2dec(priv_vbi->curr_pgno);
+    if (j>0 && j<1000 && priv_vbi->cache[j]){
+        for(i=8;i<priv_vbi->cache[j]->columns;i++){
+            page.text[i].unicode = priv_vbi->cache[j]->text[i].unicode;
+        }
+    }
+
+    if (page.text[1].unicode == ' ' && page.text[2].unicode == ' ' &&
+        page.text[3].unicode == ' ' && page.text[4].unicode == ' ' &&
+        page.text[5].unicode == ' ' && page.text[5].unicode == ' '
+        && !priv_vbi->half)
+        subtitle = 1;           // subtitle page
+    if (priv_vbi->pagenumdec) {
+        i = (priv_vbi->pagenumdec >> 12) & 0xf;
+        switch (i) {
+        case 1:
+            page.text[1].unicode = '0' + ((priv_vbi->pagenumdec >> 0) & 0xf);
+            page.text[2].unicode = '-';
+            page.text[3].unicode = '-';
+            break;
+        case 2:
+            page.text[1].unicode = '0' + ((priv_vbi->pagenumdec >> 4) & 0xf);
+            page.text[2].unicode = '0' + ((priv_vbi->pagenumdec >> 0) & 0xf);
+            page.text[3].unicode = '-';
+            break;
+        }
+        page.text[4].unicode = ' ';
+        page.text[5].unicode = ' ';
+        page.text[6].unicode = ' ';
+        page.text[1].foreground = VBI_WHITE;
+        page.text[2].foreground = VBI_WHITE;
+        page.text[3].foreground = VBI_WHITE;
+    }
+    priv_vbi->columns = page.columns;
+    priv_vbi->rows = page.rows;
+    if (!subtitle) {       // update time in header
+        memcpy(&(page.text[VBI_TIME_LINEPOS]),
+               &(priv_vbi->theader[VBI_TIME_LINEPOS]),
+               sizeof(vbi_char) * (priv_vbi->columns - VBI_TIME_LINEPOS));
+    }
+    switch (priv_vbi->tformat) {
+    case VBI_TFORMAT_TEXT:        // mode: text
+        if (priv_vbi->txtpage) {
+#ifdef USE_ICONV
+            vbi_print_page_region_nodebug(&(page), priv_vbi->txtpage, 
+                VBI_TXT_PAGE_SIZE, VBI_TEXT_CHARSET, TRUE, 
+                0, 0, 0, page.columns, page.rows);      // vbi_page to text without message
+#else
+            vbi_print_page(&(page), priv_vbi->txtpage, 
+                VBI_TXT_PAGE_SIZE, VBI_TEXT_CHARSET, TRUE, 0);
+#endif
+        }
+        priv_vbi->valid_page = 1;
+        break;
+    case VBI_TFORMAT_BW:        // mode: black & white
+        for (i=0; i < (priv_vbi->pgno!=page.pgno?page.columns:1056); i++) {
+            if (priv_vbi->foreground){
+                page.text[i].foreground = VBI_BLACK;
+                page.text[i].background = VBI_WHITE;
+	    }else{
+                page.text[i].foreground = VBI_WHITE;
+                page.text[i].background = VBI_BLACK;
+	    }
+        }
+    case VBI_TFORMAT_GRAY:        // mode: grayscale
+    case VBI_TFORMAT_COLOR:       // mode: color (request color spu patch!)
+
+
+
+        page.color_map[VBI_TRANSPARENT_COLOR] = 0;
+        if (priv_vbi->alpha) {
+            if (subtitle) {
+                for (i = 0; i < page.rows; i++) {
+                    sflg = 0;
+                    send = 0;
+                    for (j = 0; j < page.columns; j++) {
+                        if (page.text[i * page.columns + j].unicode != ' ') {
+                            sflg = 1;
+                            send = j;
+                        }
+                        if (sflg == 0)
+                            page.text[i * page.columns + j].background =
+                                VBI_TRANSPARENT_COLOR;
+                    }
+                    for (j = send + 1; j < page.columns; j++)
+                        page.text[i * page.columns + j].background =
+                            VBI_TRANSPARENT_COLOR;
+                }
+            } else {
+                for (i = 0; i < 1056; i++)
+                    page.text[i].background = VBI_TRANSPARENT_COLOR;
+            }
+        }
+        csize = page.columns * page.rows * 12 * 10 * sizeof(vbi_rgba);
+        if (csize == 0)
+            break;
+        if (csize > priv_vbi->canvas_size) {        // test canvas size
+            if (priv_vbi->canvas)
+                free(priv_vbi->canvas);
+            priv_vbi->canvas = malloc(csize);
+            priv_vbi->canvas_size = 0;
+            if (priv_vbi->canvas)
+                priv_vbi->canvas_size = csize;
+        }
+        if (priv_vbi->canvas) {
+            vbi_draw_vt_page(&(page),
+                             priv_vbi->fmt,
+                             priv_vbi->canvas,
+                             priv_vbi->reveal, priv_vbi->flash_on);
+            priv_vbi->csize = csize;
+        }
+        priv_vbi->spudec_proc = 1;
+        priv_vbi->valid_page = 1;
+        break;
+    }
+}
+
+/**
+ * \brief Update page in cache
+ * \param priv_vbi private data structure
+ *
+ * Routine also calls process_page to refresh currently visible page (if so)
+ * every time it was received from VBI by background thread.
+ *
+ */
+static void update_page(priv_vbi_t * priv_vbi)
+{
+    int i;
+    int index;
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    /*
+       priv_vbi->redraw=1   - page redraw requested
+       pgno!=page->pgno     - page was switched
+       curr_pgno==pgno      - backgound process just fetched current page, refresh it
+     */
+    if (priv_vbi->redraw ||
+        priv_vbi->pgno != priv_vbi->page->pgno ||
+        priv_vbi->curr_pgno == priv_vbi->pgno) {
+        index = vbi_bcd2dec(priv_vbi->pgno);
+        if ( index <= 0 || index > 999 || !priv_vbi->cache[index]) {
+            // curr_pgno is last decoded page
+            index = vbi_bcd2dec(priv_vbi->curr_pgno);
+        }
+
+	if (index <=0 || index >999 || !priv_vbi->cache[index]){
+            priv_vbi->valid_page = 0;
+            memset(priv_vbi->page, 0, sizeof(vbi_page));
+	}else
+	{
+            memcpy(priv_vbi->page, priv_vbi->cache[index], sizeof(vbi_page));
+            process_page(priv_vbi);//prepare page to be shown on screen
+	}
+    }
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+}
+
+/**
+ * \brief background grabber routine
+ * \param data user-defined data
+ *
+ */
+static void *grabber(void *data)
+{
+    priv_vbi_t *user_vbi = (priv_vbi_t *) data;
+    vbi_capture_buffer *sliced_buffer;
+    struct timeval timeout;
+    unsigned int n_lines;
+    int r, err_count = 0;
+
+    while (!user_vbi->eof) {
+        timeout.tv_sec = 0;
+        timeout.tv_usec = 500;
+        r = vbi_capture_pull(user_vbi->capture, NULL, &sliced_buffer, &timeout); // grab slices
+        if (user_vbi->eof)
+            return NULL;
+        switch (r) {
+        case -1: // read error
+            if (err_count++ > 4)
+                user_vbi->eof = 1;
+            break;
+        case 0:  // time out
+            break;
+        default:
+            err_count = 0;
+        }
+        if (r != 1)
+            continue;
+        n_lines = sliced_buffer->size / sizeof(vbi_sliced);
+        vbi_decode(user_vbi->decoder, (vbi_sliced *) sliced_buffer->data, 
+            n_lines, sliced_buffer->timestamp); // decode slice
+        update_page(user_vbi);
+    }
+    switch (r) {
+    case -1:
+        mp_msg(MSGT_TV, MSGL_ERR, "VBI read error %d (%s)\n",
+               errno, strerror(errno));
+        return NULL;
+    case 0:
+        mp_msg(MSGT_TV, MSGL_ERR, "VBI read timeout\n");
+        return NULL;
+    }
+    return NULL;
+}
+
+/**
+ * \brief calculate increased/decreased by given value page number
+ * \param curr  current page number in hexadecimal for
+ * \param direction decimal value (can be negative) to add to value or curr parameter
+ * \return new page number in hexadecimal form
+ *
+ * VBI page numbers are represented in special hexadecimal form, e.g.
+ * page with number 123 (as seen by user) internally has number 0x123.
+ * and equation 0x123+8 should be equal to 0x131 instead of regular 0x12b.
+ * Page numbers 0xYYY (where Y is not belongs to (0..9) and pages below 0x100 and
+ * higher 0x999 are reserved for internal use.
+ *
+ */
+static int steppage(int curr, int direction)
+{
+    int newpage = vbi_dec2bcd(vbi_bcd2dec(curr) + direction);
+    if (newpage < 0x100)
+        newpage = 0x100;
+    if (newpage > 0x999)
+        newpage = 0x999;
+    return newpage;
+}
+
+/**
+ * \brief toggles teletext page displaying mode
+ * \param priv_vbi private data structure
+ * \param flag new mode
+ * \return 
+ *   TVI_CONTROL_TRUE is success,
+ *   TVI_CONTROL_FALSE otherwise
+ *
+ * flag:
+ * 0 - off
+ * 1 - on & opaque
+ * 2 - on & transparent
+ * 3 - on & transparent  with black foreground color (only in bw mode)
+ *
+ */
+static int teletext_set_mode(priv_vbi_t * priv_vbi, int flag)
+{
+    if (flag<0 || flag>3)
+        return TVI_CONTROL_FALSE;
+        
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+
+    priv_vbi->on = flag;
+
+    if (priv_vbi->on > 2 && priv_vbi->tformat != VBI_TFORMAT_BW)
+        priv_vbi->on = 0;
+
+    priv_vbi->foreground = 0;
+    priv_vbi->pagenumdec = 0;
+    priv_vbi->spudec_proc = 1;
+    priv_vbi->redraw = 1;
+    switch (priv_vbi->on) {
+    case 0:
+        priv_vbi->csize = 0;
+        break;
+    case 1:
+        priv_vbi->alpha = 0;
+        break;
+    case 2:
+        priv_vbi->alpha = 1;
+        break;
+    case 3:
+        priv_vbi->alpha = 1;
+        priv_vbi->foreground = 1;
+        break;
+    }
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+    return TVI_CONTROL_TRUE;
+}
+
+/**
+ * \brief get half page mode (only in SPU mode)
+ * \param priv_vbi private data structure
+ * \return current mode
+ *     0 : half mode off
+ *     1 : top half page
+ *     2 : bottom half page
+ */
+static int vbi_get_half(priv_vbi_t * priv_vbi)
+{
+    int flag = 0;
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    if (priv_vbi->valid_page)
+        flag = priv_vbi->half;
+    priv_vbi->pagenumdec = 0;
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+    return flag;
+}
+
+/**
+ * \brief set half page mode (only in SPU mode)
+ * \param priv_vbi private data structure
+ * \param flag new half page mode
+ * \return 
+ *   TVI_CONTROL_TRUE is success,
+ *   TVI_CONTROL_FALSE otherwise
+ *
+ *
+ *  flag:
+ *     0 : half mode off
+ *     1 : top half page
+ *     2 : bottom half page
+ */
+static int teletext_set_half_page(priv_vbi_t * priv_vbi, int flag)
+{
+    if (flag<0 || flag>2)
+        return TVI_CONTROL_FALSE;
+
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    priv_vbi->half = flag;
+    if (priv_vbi->tformat == VBI_TFORMAT_TEXT && priv_vbi->half > 1)
+        priv_vbi->half = 0;
+    priv_vbi->redraw = 1;
+    priv_vbi->pagenumdec = 0;
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+    return TVI_CONTROL_TRUE;
+}
+
+/**
+ * \brief displays specified page
+ * \param priv_vbi private data structure
+ * \param pgno page number to display
+ * \param subno subpage number
+ *
+ */
+static void vbi_setpage(priv_vbi_t * priv_vbi, int pgno, int subno)
+{
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    priv_vbi->pgno = steppage(0, pgno);
+    priv_vbi->subno = subno;
+    priv_vbi->redraw = 1;
+    priv_vbi->pagenumdec = 0;
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+}
+
+/**
+ * \brief steps over pages by a given value
+ * \param priv_vbi private data structure
+ * \param direction decimal step value (can be negative)
+ *
+ */
+static void vbi_steppage(priv_vbi_t * priv_vbi, int direction)
+{
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    priv_vbi->pgno = steppage(priv_vbi->pgno, direction);
+    priv_vbi->redraw = 1;
+    priv_vbi->pagenumdec = 0;
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+}
+
+/**
+ * \brief append just entered digit to editing page number
+ * \param priv_vbi private data structure
+ * \param dec decimal digit to append
+ *
+ *  dec: 
+ *   '0'..'9' append digit
+ *    '-' remove last digit (backspace emulation)
+ *
+ * This routine allows user to jump to arbitrary page.
+ * It implements simple page number editing algorithm.
+ *
+ * Subsystem can be on one of two modes: normal and page number edit mode.
+ * Zero value of priv_vbi->pagenumdec means normal mode
+ * Non-zero value means page number edit mode and equals to packed
+ * decimal number of already entered part of page number.
+ *
+ * How this works.
+ * Let's assume that current mode is normal (pagenumdec is zero), teletext page 
+ * 100 are displayed as usual. topmost left corner of page contains page number.
+ * Then vbi_add_dec is sequentally called (through slave 
+ * command of course) with 1,4,-,2,3 * values of dec parameter.
+ *
+ * +-----+------------+------------------+
+ * | dec | pagenumxec | displayed number |
+ * +-----+------------+------------------+
+ * |     | 0x000      | 100              | 
+ * +-----+------------+------------------+
+ * | 1   | 0x001      | __1              |
+ * +-----+------------+------------------+
+ * | 4   | 0x014      | _14              |
+ * +-----+------------+------------------+
+ * | -   | 0x001      | __1              |
+ * +-----+------------+------------------+
+ * | 2   | 0x012      | _12              |
+ * +-----+------------+------------------+
+ * | 3   | 0x123      | 123              |
+ * +-----+------------+------------------+
+ * |     | 0x000      | 123              | 
+ * +-----+------------+------------------+
+ *
+ * pagenumdec will automatically receive zero value after third digit of page number
+ * is entered and current page will be switched to another one with entered page number.
+ *
+ */
+static void vbi_add_dec(priv_vbi_t * priv_vbi, char *dec)
+{
+    int count, shift;
+    if (!dec)
+        return;
+    if (!priv_vbi->on)
+        return;
+    if ((*dec < '0' || *dec > '9') && *dec != '-')
+        return;
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    count = (priv_vbi->pagenumdec >> 12) & 0xf;
+    if (*dec == '-') {
+        count--;
+        if (count)
+            priv_vbi->pagenumdec = ((priv_vbi->pagenumdec >> 4) & 0xfff) | (count << 12);
+        else
+            priv_vbi->pagenumdec = 0;
+    } else {
+        shift = count * 4;
+        count++;
+        priv_vbi->pagenumdec =
+            (((priv_vbi->pagenumdec) << 4 | (*dec -'0')) & 0xfff) | (count << 12);
+        if (count == 3) {
+            priv_vbi->pgno = priv_vbi->pagenumdec & 0xfff;
+            priv_vbi->subno = 0;
+            priv_vbi->redraw = 1;
+            priv_vbi->pagenumdec = 0;
+        }
+    }
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+}
+
+/**
+ * \brief follows link specified on current page
+ * \param priv_vbi private data structure
+ * \param linkno link number (0..6)
+ * \return 
+ *    TVI_CONTROL_FALSE if linkno is outside 0..6 range or if
+ *                      teletext is switched off
+ *    TVI_CONTROL_TRUE otherwise
+ *
+ * linkno: 
+ *    0: tpage in tv parameters (starting page, usually 100)
+ * 1..6: follows link on current page with given number
+ *
+ * FIXME: quick test shows that this is working strange
+ * FIXME: routine does not checks whether links exists on page or not
+ * TODO: more precise look
+ *
+ */
+static int vbi_golink(priv_vbi_t * priv_vbi, int linkno)
+{
+    if (linkno < 0 || linkno > 6)
+        return TVI_CONTROL_FALSE;
+    if (!priv_vbi->on)
+        return TVI_CONTROL_FALSE;
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    if (linkno == 0) {
+        priv_vbi->pgno = priv_vbi->tpage;
+        priv_vbi->subno = priv_vbi->page->nav_link[linkno].subno;
+        priv_vbi->redraw = 1;
+        priv_vbi->pagenumdec = 0;
+    } else {
+        linkno--;
+        if (priv_vbi->pgno == priv_vbi->page->pgno) {
+            priv_vbi->pgno = priv_vbi->page->nav_link[linkno].pgno;
+            priv_vbi->subno = priv_vbi->page->nav_link[linkno].subno;
+            priv_vbi->redraw = 1;
+            priv_vbi->pagenumdec = 0;
+        }
+    }
+    priv_vbi->pagenumdec = 0;
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+    return TVI_CONTROL_TRUE;
+}
+
+/**
+ * \brief get pointer to current teletext page
+ * \param priv_vbi private data structure
+ * \return pointer to vbi_page structure if teletext is
+ *         switched on and current page is valid, NULL - otherwise
+ *    
+ */
+static vbi_page *vbi_getpage(priv_vbi_t * priv_vbi)
+{
+    vbi_page *page = NULL;
+
+    if (!priv_vbi->on)
+        return NULL;
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    if (priv_vbi->valid_page)
+        if (page = malloc(sizeof(vbi_page)))
+            memcpy(page, priv_vbi->page, sizeof(vbi_page));
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+    return page;
+}
+
+/**
+ * \brief get pointer to current teletext page
+ * \param priv_vbi private data structure
+ * \return pointer to character string, containing text-only data of
+ *         teletext page. If teletext is switched off, current page is invalid
+ *         or page format if not equal to "text" then returning value is NULL.
+ * 
+ */
+static char *vbi_getpagetext(priv_vbi_t * priv_vbi)
+{
+    char *page = NULL;
+
+    if (!priv_vbi->on)
+        return NULL;
+    if (priv_vbi->tformat != VBI_TFORMAT_TEXT && priv_vbi->canvas)
+        return NULL;
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    if (priv_vbi->valid_page)
+        page = priv_vbi->txtpage;
+    if (!page)
+        page = priv_vbi->header;
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+    return page;
+}
+
+/**
+ * \brief get current page RGBA32 image (only in SPU mode)
+ * \param priv_vbi private data structure
+ * \return pointer to tv_teletext_img_t structure, containing among
+ *         other things rendered RGBA32 image of current teletext page.
+ *         return NULL is image is not available for some reason.
+ *
+ */
+static tv_teletext_img_t *vbi_getpageimg(priv_vbi_t * priv_vbi)
+{
+    tv_teletext_img_t *img = NULL;
+
+    if (priv_vbi->tformat == VBI_TFORMAT_TEXT)
+        return NULL;
+    if (priv_vbi->spudec_proc == 0)
+        return NULL;
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    if (NULL != (img = malloc(sizeof(tv_teletext_img_t)))) {
+        img->tformat = priv_vbi->tformat;        // format: bw|gray|color
+        img->tformat = VBI_TFORMAT_GRAY;         // format: bw|gray|color
+        img->half = priv_vbi->half;              // half mode
+        img->columns = priv_vbi->columns;        // page size
+        img->rows = priv_vbi->rows;
+        img->width = priv_vbi->columns * 12;
+        img->width = priv_vbi->rows * 10;
+        img->canvas = NULL;
+        // is page ok?
+        if (priv_vbi->canvas && priv_vbi->on && priv_vbi->csize && priv_vbi->valid_page) { 
+            
+            if (NULL != (img->canvas = malloc(priv_vbi->csize)))
+                memcpy(img->canvas, priv_vbi->canvas, priv_vbi->csize);
+        }
+    }
+    priv_vbi->spudec_proc = 0;
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+    return img;
+}
+
+/**
+ * \brief start teletext sybsystem
+ * \param priv_vbi private data structure
+ *
+ * initializes cache, vbi decoder and starts background thread
+ *
+ */
+static void vbi_start(priv_vbi_t * priv_vbi)
+{
+    if (!priv_vbi)
+        return;
+    if (NULL != (priv_vbi->txtpage = malloc(VBI_TXT_PAGE_SIZE)))        // alloc vbi_page
+        memset(priv_vbi->txtpage, 0, VBI_TXT_PAGE_SIZE);
+    priv_vbi->page = malloc(sizeof(vbi_page));
+    priv_vbi->cache = (vbi_page **) malloc(1000 * sizeof(vbi_page *));
+    memset(priv_vbi->cache, 0, 1000 * sizeof(vbi_page *));
+    priv_vbi->decoder = vbi_decoder_new();
+    priv_vbi->subno = 0;
+    priv_vbi->fmt = VBI_PIXFMT_RGBA32_LE;
+    memset(priv_vbi->theader, 0, sizeof(priv_vbi->theader));
+    snprintf(priv_vbi->header, sizeof(priv_vbi->header), "%s", VBI_NO_TELETEXT);
+    vbi_event_handler_add(priv_vbi->decoder, ~0, event_handler, (void *) priv_vbi);        // add event handler
+    pthread_create(&priv_vbi->grabber_thread, NULL, grabber, priv_vbi);        // add grab function
+    pthread_mutex_init(&priv_vbi->buffer_mutex, NULL);
+    priv_vbi->valid_page = 0;
+    priv_vbi->pagenumdec = 0;
+    mp_msg(MSGT_TV, MSGL_INFO, "Teletext device: %s\n", priv_vbi->device);
+}
+
+/**
+ * \brief Teletext reset
+ * \param priv_vbi private data structure
+ *
+ * should be called during frequency, norm change, etc
+ *
+ */
+static void vbi_reset(priv_vbi_t * priv_vbi)
+{
+    int i;
+    pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+    if (priv_vbi->canvas)
+        free(priv_vbi->canvas);
+    priv_vbi->canvas = NULL;
+    priv_vbi->canvas_size = 0;
+    priv_vbi->redraw = 1;
+    priv_vbi->csize = 0;
+    priv_vbi->valid_page = 0;
+    priv_vbi->spudec_proc = 1;
+    priv_vbi->pagenumdec = 0;
+    if (priv_vbi->page)
+        memset(priv_vbi->page, 0, sizeof(vbi_page));
+    if (priv_vbi->txtpage)
+        memset(priv_vbi->txtpage, 0, VBI_TXT_PAGE_SIZE);
+    memset(priv_vbi->theader, 0, sizeof(priv_vbi->theader));
+    if (priv_vbi->cache) {
+        for (i = 0; i < 1000; i++) {
+            if (priv_vbi->cache[i])
+                free(priv_vbi->cache[i]);
+            priv_vbi->cache[i] = NULL;
+        }
+    }
+    snprintf(priv_vbi->header, sizeof(priv_vbi->header), "%s",
+             VBI_NO_TELETEXT);
+    pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+}
+
+/*
+---------------------------------------------------------------------------------
+    Public routines
+---------------------------------------------------------------------------------
+*/
+
+/**
+ * \brief teletext subsystem init
+ * \note  Routine uses global variables tv_param_tdevice, tv_param_tpage
+ *        and tv_param_tformat for initialization.
+ *
+ */
+priv_vbi_t *teletext_init(void)
+{
+    priv_vbi_t *priv_vbi;
+    int formatid, startpage;
+    unsigned int services = VBI_SLICED_TELETEXT_B |
+             VBI_SLICED_CAPTION_525 |
+             VBI_SLICED_CAPTION_625 |
+             VBI_SLICED_VBI_525 |
+             VBI_SLICED_VBI_625 |
+             VBI_SLICED_WSS_625 |
+             VBI_SLICED_WSS_CPR1204 |
+             VBI_SLICED_VPS;
+
+    if (!tv_param_tdevice)
+        return NULL;
+
+    if (NULL == (priv_vbi = malloc(sizeof(priv_vbi_t))))
+        return NULL;
+    memset(priv_vbi, 0, sizeof(priv_vbi_t));
+    formatid = VBI_TFORMAT_TEXT;         // default
+    if (tv_param_tformat != NULL) {
+        if (strcmp(tv_param_tformat, "text") == 0)
+            formatid = VBI_TFORMAT_TEXT;
+        if (strcmp(tv_param_tformat, "bw") == 0)
+            formatid = VBI_TFORMAT_BW;
+        if (strcmp(tv_param_tformat, "gray") == 0)
+            formatid = VBI_TFORMAT_GRAY;
+        if (strcmp(tv_param_tformat, "color") == 0)
+            formatid = VBI_TFORMAT_COLOR;
+    }
+    startpage = steppage(0, tv_param_tpage);         // page number is HEX
+    if (startpage < 0x100 || startpage > 0x999)
+        startpage = 0x100;
+    priv_vbi->device = strdup(tv_param_tdevice);
+    priv_vbi->tformat = formatid;
+    priv_vbi->tpage = startpage;  // page number
+    priv_vbi->pgno = startpage;          // page number
+
+
+    if (!priv_vbi->capture) {
+        priv_vbi->services = services;  // probe v4l2
+        priv_vbi->capture = vbi_capture_v4l2_new(priv_vbi->device,      // device 
+                                                 20,                    // buffer numbers
+                                                 &(priv_vbi->services), // services
+                                                 0,                     // strict
+                                                 &(priv_vbi->errstr),   // error string
+                                                 0);                    // trace
+    }
+    services = priv_vbi->services;
+    if (priv_vbi->capture == NULL) {
+        priv_vbi->services = services;  // probe v4l
+        priv_vbi->capture = vbi_capture_v4l_new(priv_vbi->device,
+                                                20,
+                                                &(priv_vbi->services),
+                                                0, &(priv_vbi->errstr), 0);
+    }
+
+    if (!priv_vbi->capture) {
+        free(priv_vbi->device);
+        free(priv_vbi);
+        mp_msg(MSGT_TV, MSGL_INFO, "No teletext\n");
+        return NULL;
+    }
+    return priv_vbi;
+}
+
+/**
+ * \brief teletext subsystem uninitialization
+ * \param priv_vbi private data structure
+ *
+ * closes vbi capture, decode and and frees priv_vbi structure
+ *
+ */
+void teletext_uninit(priv_vbi_t * priv_vbi)
+{
+    int i;
+    if (priv_vbi == NULL)
+        return;
+    priv_vbi->eof = 1;
+    if (priv_vbi->capture){
+        vbi_capture_delete(priv_vbi->capture);
+        priv_vbi->capture = NULL;
+    }
+    if (priv_vbi->decoder){
+        vbi_event_handler_remove(priv_vbi->decoder, event_handler);
+        vbi_decoder_delete(priv_vbi->decoder);
+        priv_vbi->decoder = NULL;
+    }
+    if (priv_vbi->grabber_thread)
+        pthread_join(priv_vbi->grabber_thread, NULL);
+    pthread_mutex_destroy(&priv_vbi->buffer_mutex);
+    if (priv_vbi->device){
+        free(priv_vbi->device);
+        priv_vbi->device = NULL;
+    }
+    if (priv_vbi->errstr){
+        free(priv_vbi->errstr);
+        priv_vbi->errstr = NULL;
+    }
+    if (priv_vbi->canvas){
+        free(priv_vbi->canvas);
+        priv_vbi->canvas = NULL;
+    }
+    if (priv_vbi->txtpage){
+        free(priv_vbi->txtpage);
+        priv_vbi->txtpage = NULL;
+    }
+    if (priv_vbi->network_name){
+        free(priv_vbi->network_name);
+        priv_vbi->network_name = NULL;
+    }
+    if (priv_vbi->network_id){
+        free(priv_vbi->network_id);
+        priv_vbi->network_id = NULL;
+    }
+    if (priv_vbi->page){
+        free(priv_vbi->page);
+        priv_vbi->page = NULL;
+    }
+    if (priv_vbi->cache) {
+        for (i = 0; i < 1000; i++) {
+            if (priv_vbi->cache[i])
+                free(priv_vbi->cache[i]);
+        }
+        free(priv_vbi->cache);
+        priv_vbi->cache = NULL;
+    }
+    free(priv_vbi);
+}
+
+/**
+ * \brief Teletext control routine
+ * \param priv_vbi private data structure
+ * \param cmd command 
+ * \param arg command parameter (has to be not null)
+ *
+ */
+int teletext_control(priv_vbi_t * priv_vbi, int cmd, void *arg)
+{
+    vbi_page *page = NULL;
+    char *txtpage = NULL;
+    tv_teletext_img_t *img = NULL;
+    if (!priv_vbi)
+        return TVI_CONTROL_FALSE;
+    if (!arg)
+        return TVI_CONTROL_FALSE;
+    switch (cmd) {
+    case TVI_CONTROL_VBI_RESET:
+        vbi_reset(priv_vbi);
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_START:
+        vbi_start(priv_vbi);
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_GET_FORMAT:
+        pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+        *(int*)arg=priv_vbi->tformat;
+        pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+	return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_SET_MODE:
+        return teletext_set_mode(priv_vbi, *(int *) arg);
+    case TVI_CONTROL_VBI_GET_MODE:
+        pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+        *(int*)arg=priv_vbi->on;
+        pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+	return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_STEP_MODE:
+    {
+        int val;
+        pthread_mutex_lock(&(priv_vbi->buffer_mutex));
+	val=(priv_vbi->on+*(int*)arg)%4;
+        pthread_mutex_unlock(&(priv_vbi->buffer_mutex));
+	if (val<0)
+	    val+=4;
+	return teletext_set_mode(priv_vbi,val);
+    }
+    case TVI_CONTROL_VBI_GET_HALF_PAGE:
+        *(void **) arg = (void *) vbi_get_half(priv_vbi);
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_SET_HALF_PAGE:
+        return teletext_set_half_page(priv_vbi, *(int *) arg);
+    case TVI_CONTROL_VBI_STEP_HALF_PAGE:
+    {
+        int val;
+	val=(vbi_get_half(priv_vbi)+*(int*)arg)%3;
+	
+	if (val<0)
+	    val+=3;
+	return teletext_set_half_page(priv_vbi,val);
+    }
+
+    case TVI_CONTROL_VBI_SET_PAGE:
+        vbi_setpage(priv_vbi, *(int *) arg, 0);
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_STEP_PAGE:
+        vbi_steppage(priv_vbi, *(int *) arg);
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_ADD_DEC:
+        vbi_add_dec(priv_vbi, *(char **) arg);
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_GO_LINK:
+        return vbi_golink(priv_vbi, *(int *) arg);
+    case TVI_CONTROL_VBI_GET_PAGE:
+        *(int*) arg = priv_vbi->pgno;
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_GET_VBIPAGE:
+        if (NULL == (page = vbi_getpage(priv_vbi)))
+            return TVI_CONTROL_FALSE;
+        *(void **) arg = (void *) page;
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_GET_TXTPAGE:
+        if (NULL == (txtpage = vbi_getpagetext(priv_vbi)))
+            return TVI_CONTROL_FALSE;
+        *(void **) arg = (void *) txtpage;
+        return TVI_CONTROL_TRUE;
+    case TVI_CONTROL_VBI_GET_IMGPAGE:
+        if (NULL == (img = vbi_getpageimg(priv_vbi)))
+            return TVI_CONTROL_FALSE;
+        *(void **) arg = (void *) img;
+        return TVI_CONTROL_TRUE;
+    }
+    return TVI_CONTROL_UNKNOWN;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stream/tvi_vbi.h	Sun Jun 10 00:06:12 2007 +0000
@@ -0,0 +1,75 @@
+#ifndef __TVI_VBI_H_
+#define __TVI_VBI_H_
+
+#include "libzvbi.h"
+#include "libmpcodecs/img_format.h"
+#include "libmpcodecs/mp_image.h"
+#include "tv.h"
+
+#define VBI_MAX_SUBPAGES   64               ///< max sub pages number
+#define VBI_TXT_PAGE_SIZE  42*25*2          ///< max text page size
+#define VBI_MAX_LINE_SIZE  42               ///< max line size in text page
+
+#define VBI_TFORMAT_TEXT    0               ///< text mode
+#define VBI_TFORMAT_BW      1               ///< back&white mode
+#define VBI_TFORMAT_GRAY    2               ///< grayscale mode
+#define VBI_TFORMAT_COLOR   3               ///< color mode (require color_spu patch!)
+
+#define VBI_NO_TELETEXT    "No teletext"
+
+#define VBI_TRANSPARENT_COLOR    40         ///< transparent color id
+#define VBI_TIME_LINEPOS    13              ///< time line pos in page header
+
+typedef struct {
+    int            on;                      ///< teletext on/off
+
+    char*        device;                    ///< capture device
+    unsigned int    services;               ///< services
+    vbi_capture*    capture;                ///< vbi_capture
+    int            capture_fd;              ///< capture fd (now not used)
+    vbi_decoder*    decoder;                ///< vbi_decoder
+    char*        errstr;                    ///< error string
+    pthread_t        grabber_thread;        ///< grab thread
+    pthread_mutex_t    buffer_mutex;
+    pthread_mutex_t    update_mutex;
+    int            eof;                     ///< end grab
+    int           tpage;                    ///< tpage
+    int            pgno;                    ///< seek page number
+    int            subno;                   ///< seek subpage
+    int            curr_pgno;               ///< current page number
+    int            curr_subno;              ///< current subpage
+    uint32_t       pagenumdec;              ///< set page num with dec
+
+    vbi_page** cache;
+    vbi_page         *page;                 ///< vbi_page
+    int            valid_page;              ///< valid page flag
+    char*        txtpage;                   ///< decoded vbi_page to text
+    vbi_char    theader[VBI_MAX_LINE_SIZE]; ///< vbi header
+    char        header[VBI_MAX_LINE_SIZE];  ///< text header
+
+    int            tformat;                 ///< 0:text, 1:bw, 2:gray, 3:color
+    vbi_pixfmt         fmt;                 ///< image format (only VBI_PIXFMT_RGBA32_LE supported)
+    void*        canvas;                    ///< stored image data
+    int            csize;                   ///< stored image size
+    int            canvas_size;             ///< image buffer size
+    int            reveal;                  ///< reveal (now not used)
+    int            flash_on;                ///< flash_on (now not used)
+    int            alpha;                   ///< opacity mode
+    int            foreground;              ///< foreground black in bw mode
+    int            half;                    ///< 0:half mode off, 1:top half page, 2:bottom half page
+    int            redraw;                  ///< is redraw last image
+    int            columns;                 ///< page size: coloumns
+    int            rows;                    ///< page size: rows
+    int            spudec_proc;             ///< render image request
+
+    char*        network_name;              ///< network name
+    char*        network_id;                ///< network id
+    } priv_vbi_t;
+
+/// teletext subsystem initialization
+priv_vbi_t* teletext_init(void);
+/// teletext subsystem uninitialization
+void teletext_uninit(priv_vbi_t* priv_vbi);
+/// ioctl for 
+int teletext_control(priv_vbi_t* priv_vbi, int cmd, void *args);
+#endif