summaryrefslogtreecommitdiffstats
path: root/stream/tvi_vbi.c
diff options
context:
space:
mode:
authorvoroshil <voroshil@b3059339-0415-0410-9bf9-f77b7e298cf2>2007-06-10 00:06:12 +0000
committervoroshil <voroshil@b3059339-0415-0410-9bf9-f77b7e298cf2>2007-06-10 00:06:12 +0000
commit83a2c50ef9ef648b576bf4299765c73212d1f480 (patch)
treeab09f836c3ca91641c96b0630b2b2f479a3a6c2f /stream/tvi_vbi.c
parent990b33b1c28d6b604a85e588689ad7fb6de59354 (diff)
downloadmpv-83a2c50ef9ef648b576bf4299765c73212d1f480.tar.bz2
mpv-83a2c50ef9ef648b576bf4299765c73212d1f480.tar.xz
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 git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@23530 b3059339-0415-0410-9bf9-f77b7e298cf2
Diffstat (limited to 'stream/tvi_vbi.c')
-rw-r--r--stream/tvi_vbi.c1197
1 files changed, 1197 insertions, 0 deletions
diff --git a/stream/tvi_vbi.c b/stream/tvi_vbi.c
new file mode 100644
index 0000000000..226cf9b342
--- /dev/null
+++ b/stream/tvi_vbi.c
@@ -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)