view stream/tvi_vbi.c @ 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
children
line wrap: on
line source

#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;
}