view stream/tvi_vbi.c @ 23542:2e13eea875a3

Copile fix: missing libswscale part of ffmpeg r9322 TARGET_ARCH -> ARCH change.
author reimar
date Sat, 16 Jun 2007 15:17:00 +0000
parents a6c619ee9d30
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;
}