]> git.sesse.net Git - vlc/blob - modules/codec/zvbi.c
zvbi: mimic cc.c for using subtitles updater etc to avoid flickering in text subtitles
[vlc] / modules / codec / zvbi.c
1 /*****************************************************************************
2  * zvbi.c : VBI and Teletext PES demux and decoder using libzvbi
3  *****************************************************************************
4  * Copyright (C) 2007, M2X
5  * $Id$
6  *
7  * Authors: Derk-Jan Hartman <djhartman at m2x dot nl>
8  *          Jean-Paul Saman <jpsaman at m2x dot nl>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  *
27  * information on teletext format can be found here :
28  * http://pdc.ro.nu/teletext.html
29  *
30  *****************************************************************************/
31
32 /* This module implements:
33  * ETSI EN 301 775: VBI data in PES
34  * ETSI EN 300 472: EBU Teletext data in PES
35  * ETSI EN 300 706: Enhanced Teletext (libzvbi)
36  * ETSI EN 300 231: Video Programme System [VPS] (libzvbi)
37  * ETSI EN 300 294: 625-line Wide Screen Signaling [WSS] (libzvbi)
38  * EIA-608 Revision A: Closed Captioning [CC] (libzvbi)
39  */
40
41 #ifdef HAVE_CONFIG_H
42 # include "config.h"
43 #endif
44
45 #include <ctype.h>
46
47 #include <vlc_common.h>
48 #include <vlc_plugin.h>
49 #include <assert.h>
50 #include <libzvbi.h>
51
52 #include <vlc_codec.h>
53 #include "substext.h"
54
55 /*****************************************************************************
56  * Module descriptor.
57  *****************************************************************************/
58 static int  Open ( vlc_object_t * );
59 static void Close( vlc_object_t * );
60
61 #define PAGE_TEXT N_("Teletext page")
62 #define PAGE_LONGTEXT N_("Open the indicated Teletext page." \
63         "Default page is index 100")
64
65 #define OPAQUE_TEXT N_("Teletext transparency")
66 #define OPAQUE_LONGTEXT N_("Setting vbi-opaque to false " \
67         "makes the boxed text transparent." )
68
69 #define POS_TEXT N_("Teletext alignment")
70 #define POS_LONGTEXT N_( \
71   "You can enforce the teletext position on the video " \
72   "(0=center, 1=left, 2=right, 4=top, 8=bottom, you can " \
73   "also use combinations of these values, eg. 6 = top-right).")
74
75 #define TELX_TEXT N_("Teletext text subtitles")
76 #define TELX_LONGTEXT N_( "Output teletext subtitles as text " \
77   "instead of as RGBA" )
78
79 static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
80 static const char *const ppsz_pos_descriptions[] =
81 { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
82   N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
83
84 vlc_module_begin ()
85     set_description( N_("VBI and Teletext decoder") )
86     set_shortname( N_("VBI & Teletext") )
87     set_capability( "decoder", 51 )
88     set_category( CAT_INPUT )
89     set_subcategory( SUBCAT_INPUT_SCODEC )
90     set_callbacks( Open, Close )
91
92     add_integer( "vbi-page", 100,
93                  PAGE_TEXT, PAGE_LONGTEXT, false )
94     add_bool( "vbi-opaque", true,
95                  OPAQUE_TEXT, OPAQUE_LONGTEXT, false )
96     add_integer( "vbi-position", 8, POS_TEXT, POS_LONGTEXT, false )
97         change_integer_list( pi_pos_values, ppsz_pos_descriptions );
98     add_bool( "vbi-text", false,
99               TELX_TEXT, TELX_LONGTEXT, false )
100 vlc_module_end ()
101
102 /****************************************************************************
103  * Local structures
104  ****************************************************************************/
105
106 // #define ZVBI_DEBUG
107
108 //Guessing table for missing "default region triplet"
109 static const int pi_default_triplet[] = {
110  0, 0, 0, 0,     // slo slk cze ces
111  8,              // pol
112  24,24,24,24,24, //scc scr srp hrv slv
113  24,24,          //rum ron
114  32,32,32,32,32, //est lit rus bul ukr
115  48,48,          //gre ell
116  64,             //ara
117  88,             //heb
118  16 };           //default
119 static const char *const ppsz_default_triplet[] = {
120  "slo", "slk", "cze", "ces",
121  "pol",
122  "scc", "scr", "srp", "hrv", "slv",
123  "rum", "ron",
124  "est", "lit", "rus", "bul", "ukr",
125  "gre", "ell",
126  "ara",
127  "heb",
128  NULL
129 };
130
131 typedef enum {
132     ZVBI_KEY_RED    = 'r' << 16,
133     ZVBI_KEY_GREEN  = 'g' << 16,
134     ZVBI_KEY_YELLOW = 'y' << 16,
135     ZVBI_KEY_BLUE   = 'b' << 16,
136     ZVBI_KEY_INDEX  = 'i' << 16,
137 } ttxt_key_id;
138
139 #define MAX_SLICES 32
140
141 struct decoder_sys_t
142 {
143     vbi_decoder *     p_vbi_dec;
144     vbi_sliced        p_vbi_sliced[MAX_SLICES];
145     unsigned int      i_last_page;
146     bool              b_update;
147     bool              b_text;   /* Subtitles as text */
148
149     vlc_mutex_t       lock; /* Lock to protect the following variables */
150     /* Positioning of Teletext images */
151     int               i_align;
152     /* */
153     unsigned int      i_wanted_page;
154     unsigned int      i_wanted_subpage;
155     /* */
156     bool              b_opaque;
157     struct {
158         int pgno, subno;
159     }                 nav_link[6];
160     int               i_key[3];
161 };
162
163 static subpicture_t *Decode( decoder_t *, block_t ** );
164
165 static subpicture_t *Subpicture( decoder_t *p_dec, video_format_t *p_fmt,
166                                  bool b_text,
167                                  int i_columns, int i_rows,
168                                  int i_align, mtime_t i_pts );
169
170 static void EventHandler( vbi_event *ev, void *user_data );
171 static int OpaquePage( picture_t *p_src, const vbi_page *p_page,
172                        const video_format_t fmt, bool b_opaque, const int text_offset );
173 static int get_first_visible_row( vbi_char *p_text, int rows, int columns);
174 static int get_last_visible_row( vbi_char *p_text, int rows, int columns);
175
176 /* Properties callbacks */
177 static int RequestPage( vlc_object_t *p_this, char const *psz_cmd,
178                         vlc_value_t oldval, vlc_value_t newval, void *p_data );
179 static int Opaque( vlc_object_t *p_this, char const *psz_cmd,
180                    vlc_value_t oldval, vlc_value_t newval, void *p_data );
181 static int Position( vlc_object_t *p_this, char const *psz_cmd,
182                      vlc_value_t oldval, vlc_value_t newval, void *p_data );
183 static int EventKey( vlc_object_t *p_this, char const *psz_cmd,
184                      vlc_value_t oldval, vlc_value_t newval, void *p_data );
185
186 /*****************************************************************************
187  * Open: probe the decoder and return score
188  *****************************************************************************
189  * Tries to launch a decoder and return score so that the interface is able
190  * to chose.
191  *****************************************************************************/
192 static int Open( vlc_object_t *p_this )
193 {
194     decoder_t     *p_dec = (decoder_t *) p_this;
195     decoder_sys_t *p_sys = NULL;
196
197     if( p_dec->fmt_in.i_codec != VLC_CODEC_TELETEXT )
198         return VLC_EGENERIC;
199
200     p_sys = p_dec->p_sys = calloc( 1, sizeof(decoder_sys_t) );
201     if( p_sys == NULL )
202         return VLC_ENOMEM;
203
204     p_sys->i_key[0] = p_sys->i_key[1] = p_sys->i_key[2] = '*' - '0';
205     p_sys->b_update = false;
206     p_sys->p_vbi_dec = vbi_decoder_new();
207     vlc_mutex_init( &p_sys->lock );
208
209     if( p_sys->p_vbi_dec == NULL )
210     {
211         msg_Err( p_dec, "VBI decoder could not be created." );
212         Close( p_this );
213         return VLC_ENOMEM;
214     }
215
216     /* Some broadcasters in countries with level 1 and level 1.5 still not send a G0 to do 
217      * matches against table 32 of ETSI 300 706. We try to do some best effort guessing
218      * This is not perfect, but might handle some cases where we know the vbi language 
219      * is known. It would be better if people started sending G0 */
220     for( int i = 0; ppsz_default_triplet[i] != NULL; i++ )
221     {
222         if( p_dec->fmt_in.psz_language && !strcasecmp( p_dec->fmt_in.psz_language, ppsz_default_triplet[i] ) )
223         {
224             vbi_teletext_set_default_region( p_sys->p_vbi_dec, pi_default_triplet[i]);
225             msg_Dbg( p_dec, "overwriting default zvbi region: %d", pi_default_triplet[i] );
226         }
227     }
228
229     vbi_event_handler_register( p_sys->p_vbi_dec, VBI_EVENT_TTX_PAGE | VBI_EVENT_NETWORK |
230 #ifdef ZVBI_DEBUG
231                                 VBI_EVENT_CAPTION | VBI_EVENT_TRIGGER |
232                                 VBI_EVENT_ASPECT | VBI_EVENT_PROG_INFO | VBI_EVENT_NETWORK_ID |
233 #endif
234                                 0 , EventHandler, p_dec );
235
236     /* Create the var on vlc_global. */
237     p_sys->i_wanted_page = var_CreateGetInteger( p_dec, "vbi-page" );
238     var_AddCallback( p_dec, "vbi-page", RequestPage, p_sys );
239
240     /* Check if the Teletext track has a known "initial page". */
241     if( p_sys->i_wanted_page == 100 && p_dec->fmt_in.subs.teletext.i_magazine != -1 )
242     {
243         p_sys->i_wanted_page = 100 * p_dec->fmt_in.subs.teletext.i_magazine +
244                                vbi_bcd2dec( p_dec->fmt_in.subs.teletext.i_page );
245         var_SetInteger( p_dec, "vbi-page", p_sys->i_wanted_page );
246     }
247     p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
248
249     p_sys->b_opaque = var_CreateGetBool( p_dec, "vbi-opaque" );
250     var_AddCallback( p_dec, "vbi-opaque", Opaque, p_sys );
251
252     p_sys->i_align = var_CreateGetInteger( p_dec, "vbi-position" );
253     var_AddCallback( p_dec, "vbi-position", Position, p_sys );
254
255     p_sys->b_text = var_CreateGetBool( p_dec, "vbi-text" );
256 //    var_AddCallback( p_dec, "vbi-text", Text, p_sys );
257
258     /* Listen for keys */
259     var_AddCallback( p_dec->p_libvlc, "key-pressed", EventKey, p_dec );
260
261     es_format_Init( &p_dec->fmt_out, SPU_ES, VLC_CODEC_SPU );
262     if( p_sys->b_text )
263         p_dec->fmt_out.video.i_chroma = VLC_CODEC_TEXT;
264     else
265         p_dec->fmt_out.video.i_chroma = VLC_CODEC_RGBA;
266
267     p_dec->pf_decode_sub = Decode;
268     return VLC_SUCCESS;
269 }
270
271 /*****************************************************************************
272  * Close:
273  *****************************************************************************/
274 static void Close( vlc_object_t *p_this )
275 {
276     decoder_t     *p_dec = (decoder_t*) p_this;
277     decoder_sys_t *p_sys = p_dec->p_sys;
278
279     var_DelCallback( p_dec, "vbi-position", Position, p_sys );
280     var_DelCallback( p_dec, "vbi-opaque", Opaque, p_sys );
281     var_DelCallback( p_dec, "vbi-page", RequestPage, p_sys );
282     var_DelCallback( p_dec->p_libvlc, "key-pressed", EventKey, p_dec );
283
284     vlc_mutex_destroy( &p_sys->lock );
285
286     if( p_sys->p_vbi_dec )
287         vbi_decoder_delete( p_sys->p_vbi_dec );
288     free( p_sys );
289 }
290
291 #ifdef WORDS_BIGENDIAN
292 # define ZVBI_PIXFMT_RGBA32 VBI_PIXFMT_RGBA32_BE
293 #else
294 # define ZVBI_PIXFMT_RGBA32 VBI_PIXFMT_RGBA32_LE
295 #endif
296
297 /*****************************************************************************
298  * Decode:
299  *****************************************************************************/
300 static subpicture_t *Decode( decoder_t *p_dec, block_t **pp_block )
301 {
302     decoder_sys_t   *p_sys = p_dec->p_sys;
303     block_t         *p_block;
304     subpicture_t    *p_spu = NULL;
305     video_format_t  fmt;
306     bool            b_cached = false;
307     vbi_page        p_page;
308
309     if( (pp_block == NULL) || (*pp_block == NULL) )
310         return NULL;
311
312     p_block = *pp_block;
313     *pp_block = NULL;
314
315     if( p_block->i_buffer > 0 &&
316         ( ( p_block->p_buffer[0] >= 0x10 && p_block->p_buffer[0] <= 0x1f ) ||
317           ( p_block->p_buffer[0] >= 0x99 && p_block->p_buffer[0] <= 0x9b ) ) )
318     {
319         vbi_sliced   *p_sliced = p_sys->p_vbi_sliced;
320         unsigned int i_lines = 0;
321
322         p_block->i_buffer--;
323         p_block->p_buffer++;
324         while( p_block->i_buffer >= 2 )
325         {
326             int      i_id   = p_block->p_buffer[0];
327             unsigned i_size = p_block->p_buffer[1];
328
329             if( 2 + i_size > p_block->i_buffer )
330                 break;
331
332             if( ( i_id == 0x02 || i_id == 0x03 ) && i_size >= 44 && i_lines < MAX_SLICES )
333             {
334                 unsigned line_offset  = p_block->p_buffer[2] & 0x1f;
335                 unsigned field_parity = p_block->p_buffer[2] & 0x20;
336
337                 p_sliced[i_lines].id = VBI_SLICED_TELETEXT_B;
338                 if( line_offset > 0 )
339                     p_sliced[i_lines].line = line_offset + (field_parity ? 0 : 313);
340                 else
341                     p_sliced[i_lines].line = 0;
342                 for( int i = 0; i < 42; i++ )
343                     p_sliced[i_lines].data[i] = vbi_rev8( p_block->p_buffer[4 + i] );
344                 i_lines++;
345             }
346
347             p_block->i_buffer -= 2 + i_size;
348             p_block->p_buffer += 2 + i_size;
349         }
350
351         if( i_lines > 0 )
352             vbi_decode( p_sys->p_vbi_dec, p_sliced, i_lines, 0 );
353     }
354
355     /* */
356     vlc_mutex_lock( &p_sys->lock );
357     const int i_align = p_sys->i_align;
358     const unsigned int i_wanted_page = p_sys->i_wanted_page;
359     const unsigned int i_wanted_subpage = p_sys->i_wanted_subpage;
360     const bool b_opaque = p_sys->b_opaque;
361     vlc_mutex_unlock( &p_sys->lock );
362
363     /* Try to see if the page we want is in the cache yet */
364     memset( &p_page, 0, sizeof(vbi_page) );
365     b_cached = vbi_fetch_vt_page( p_sys->p_vbi_dec, &p_page,
366                                   vbi_dec2bcd( i_wanted_page ),
367                                   i_wanted_subpage, VBI_WST_LEVEL_3p5,
368                                   25, true );
369
370     if( i_wanted_page == p_sys->i_last_page && !p_sys->b_update )
371         goto error;
372
373     if( !b_cached )
374     {
375         if( p_sys->i_last_page != i_wanted_page )
376         {
377             /* We need to reset the subtitle */
378             p_spu = Subpicture( p_dec, &fmt, true,
379                                 p_page.columns, p_page.rows,
380                                 i_align, p_block->i_pts );
381             if( !p_spu )
382                 goto error;
383             subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
384             p_spu_sys->text = strdup("");
385
386             p_sys->b_update = true;
387             p_sys->i_last_page = i_wanted_page;
388             goto exit;
389         }
390         goto error;
391     }
392
393     p_sys->b_update = false;
394     p_sys->i_last_page = i_wanted_page;
395 #ifdef ZVBI_DEBUG
396     msg_Dbg( p_dec, "we now have page: %d ready for display",
397              i_wanted_page );
398 #endif
399
400     /* Ignore transparent rows at the beginning and end */
401     int i_first_row = get_first_visible_row( p_page.text, p_page.rows, p_page.columns );
402     if ( i_first_row < 0 )
403         goto error;
404     int i_num_rows = get_last_visible_row( p_page.text, p_page.rows, p_page.columns ) - i_first_row + 1;
405 #ifdef ZVBI_DEBUG
406     msg_Dbg( p_dec, "After top and tail of page we have rows %i-%i of %i",
407              i_first_row + 1, i_first_row + i_num_rows, p_page.rows );
408 #endif
409
410     /* If there is a page or sub to render, then we do that here */
411     /* Create the subpicture unit */
412     p_spu = Subpicture( p_dec, &fmt, p_sys->b_text,
413                         p_page.columns, i_num_rows,
414                         i_align, p_block->i_pts );
415     if( !p_spu )
416         goto error;
417
418     if( p_sys->b_text )
419     {
420         unsigned int i_textsize = 7000;
421         int i_total,offset;
422         char p_text[i_textsize+1];
423
424         i_total = vbi_print_page_region( &p_page, p_text, i_textsize,
425                         "UTF-8", 0, 0, 0, i_first_row, p_page.columns, i_num_rows );
426
427         for( offset=1; offset<i_total && isspace( p_text[i_total-offset ] ); offset++)
428            p_text[i_total-offset] = '\0';
429
430         i_total -= offset;
431
432         offset=0;
433         while( offset < i_total && isspace( p_text[offset] ) )
434            offset++;
435
436         subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
437         p_spu_sys->text = strdup( &p_text[offset] );
438
439         p_spu_sys->align = i_align;
440         p_spu_sys->i_font_height_percent = 5;
441
442 #ifdef ZVBI_DEBUG
443         msg_Info( p_dec, "page %x-%x(%d)\n\"%s\"", p_page.pgno, p_page.subno, i_total, &p_text[offset] );
444 #endif
445     }
446     else
447     {
448         picture_t *p_pic = p_spu->p_region->p_picture;
449
450         /* ZVBI is stupid enough to assume pitch == width */
451         p_pic->p->i_pitch = 4 * fmt.i_width;
452
453         /* Maintain subtitle postion */
454         p_spu->p_region->i_y = i_first_row*10;
455         p_spu->i_original_picture_width = p_page.columns*12;
456         p_spu->i_original_picture_height = p_page.rows*10;
457
458         vbi_draw_vt_page_region( &p_page, ZVBI_PIXFMT_RGBA32,
459                           p_spu->p_region->p_picture->p->p_pixels, -1,
460                           0, i_first_row, p_page.columns, i_num_rows,
461                           1, 1);
462
463         vlc_mutex_lock( &p_sys->lock );
464         memcpy( p_sys->nav_link, &p_page.nav_link, sizeof( p_sys->nav_link )) ;
465         vlc_mutex_unlock( &p_sys->lock );
466
467         OpaquePage( p_pic, &p_page, fmt, b_opaque, i_first_row * p_page.columns );
468     }
469
470 exit:
471     vbi_unref_page( &p_page );
472     block_Release( p_block );
473     return p_spu;
474
475 error:
476     vbi_unref_page( &p_page );
477     if( p_spu != NULL )
478     {
479         decoder_DeleteSubpicture( p_dec, p_spu );
480         p_spu = NULL;
481     }
482
483     block_Release( p_block );
484     return NULL;
485 }
486
487 static subpicture_t *Subpicture( decoder_t *p_dec, video_format_t *p_fmt,
488                                  bool b_text,
489                                  int i_columns, int i_rows, int i_align,
490                                  mtime_t i_pts )
491 {
492     video_format_t fmt;
493     subpicture_t *p_spu=NULL;
494
495     /* If there is a page or sub to render, then we do that here */
496     /* Create the subpicture unit */
497     if( b_text )
498         p_spu = decoder_NewSubpictureText( p_dec );
499     else
500         p_spu = decoder_NewSubpicture( p_dec, NULL );
501     if( !p_spu )
502     {
503         msg_Warn( p_dec, "can't get spu buffer" );
504         return NULL;
505     }
506
507     memset( &fmt, 0, sizeof(video_format_t) );
508     fmt.i_chroma = b_text ? VLC_CODEC_TEXT : VLC_CODEC_RGBA;
509     fmt.i_sar_num = 0;
510     fmt.i_sar_den = 1;
511     if( b_text )
512     {
513         fmt.i_bits_per_pixel = 0;
514     }
515     else
516     {
517         fmt.i_width = fmt.i_visible_width = i_columns * 12;
518         fmt.i_height = fmt.i_visible_height = i_rows * 10;
519         fmt.i_bits_per_pixel = 32;
520     }
521     fmt.i_x_offset = fmt.i_y_offset = 0;
522
523     p_spu->p_region = subpicture_region_New( &fmt );
524     if( p_spu->p_region == NULL )
525     {
526         msg_Err( p_dec, "cannot allocate SPU region" );
527         decoder_DeleteSubpicture( p_dec, p_spu );
528         return NULL;
529     }
530
531     p_spu->p_region->i_x = 0;
532     p_spu->p_region->i_y = 0;
533
534     p_spu->i_start = i_pts;
535     p_spu->i_stop = i_pts + 10000000;
536     p_spu->b_ephemer = true;
537     p_spu->b_absolute = b_text ? false : true;
538
539     if( !b_text )
540         p_spu->p_region->i_align = i_align;
541     p_spu->i_original_picture_width = fmt.i_width;
542     p_spu->i_original_picture_height = fmt.i_height;
543
544     /* */
545     *p_fmt = fmt;
546     return p_spu;
547 }
548
549 static void EventHandler( vbi_event *ev, void *user_data )
550 {
551     decoder_t *p_dec        = (decoder_t *)user_data;
552     decoder_sys_t *p_sys    = p_dec->p_sys;
553
554     if( ev->type == VBI_EVENT_TTX_PAGE )
555     {
556 #ifdef ZVBI_DEBUG
557         msg_Info( p_dec, "Page %03x.%02x ",
558                     ev->ev.ttx_page.pgno,
559                     ev->ev.ttx_page.subno & 0xFF);
560 #endif
561         if( p_sys->i_last_page == vbi_bcd2dec( ev->ev.ttx_page.pgno ) )
562             p_sys->b_update = true;
563 #ifdef ZVBI_DEBUG
564         if( ev->ev.ttx_page.clock_update )
565             msg_Dbg( p_dec, "clock" );
566         if( ev->ev.ttx_page.header_update )
567             msg_Dbg( p_dec, "header" );
568 #endif
569     }
570     else if( ev->type == VBI_EVENT_CLOSE )
571         msg_Dbg( p_dec, "Close event" );
572     else if( ev->type == VBI_EVENT_CAPTION )
573         msg_Dbg( p_dec, "Caption line: %x", ev->ev.caption.pgno );
574     else if( ev->type == VBI_EVENT_NETWORK )
575     {
576         msg_Dbg( p_dec, "Network change");
577         vbi_network n = ev->ev.network;
578         msg_Dbg( p_dec, "Network id:%d name: %s, call: %s ", n.nuid, n.name, n.call );
579     }
580     else if( ev->type == VBI_EVENT_TRIGGER )
581         msg_Dbg( p_dec, "Trigger event" );
582     else if( ev->type == VBI_EVENT_ASPECT )
583         msg_Dbg( p_dec, "Aspect update" );
584     else if( ev->type == VBI_EVENT_PROG_INFO )
585         msg_Dbg( p_dec, "Program info received" );
586     else if( ev->type == VBI_EVENT_NETWORK_ID )
587         msg_Dbg( p_dec, "Network ID changed" );
588 }
589
590 static int get_first_visible_row( vbi_char *p_text, int rows, int columns)
591 {
592     for ( int i = 0; i < rows * columns; i++ )
593     {
594         if ( p_text[i].opacity != VBI_TRANSPARENT_SPACE )
595         {
596             return i / columns;
597         }
598     }
599
600     return rows;
601 }
602
603 static int get_last_visible_row( vbi_char *p_text, int rows, int columns)
604 {
605     for ( int i = rows * columns - 1; i >= 0; i-- )
606     {
607         if (p_text[i].opacity != VBI_TRANSPARENT_SPACE)
608         {
609             return ( i + columns - 1) / columns;
610         }
611     }
612
613     return 0;
614 }
615
616 static int OpaquePage( picture_t *p_src, const vbi_page *p_page,
617                        const video_format_t fmt, bool b_opaque, const int text_offset )
618 {
619     unsigned int    x, y;
620
621     assert( fmt.i_chroma == VLC_CODEC_RGBA );
622
623     /* Kludge since zvbi doesn't provide an option to specify opacity. */
624     for( y = 0; y < fmt.i_height; y++ )
625     {
626         for( x = 0; x < fmt.i_width; x++ )
627         {
628             const vbi_opacity opacity = p_page->text[ text_offset + y/10 * p_page->columns + x/12 ].opacity;
629             const int background = p_page->text[ text_offset + y/10 * p_page->columns + x/12 ].background;
630             uint32_t *p_pixel = (uint32_t*)&p_src->p->p_pixels[y * p_src->p->i_pitch + 4*x];
631
632             switch( opacity )
633             {
634             /* Show video instead of this character */
635             case VBI_TRANSPARENT_SPACE:
636                 *p_pixel = 0;
637                 break;
638             /* Display foreground and background color */
639             /* To make the boxed text "closed captioning" transparent
640              * change true to false.
641              */
642             case VBI_OPAQUE:
643             /* alpha blend video into background color */
644             case VBI_SEMI_TRANSPARENT:
645                 if( b_opaque )
646                     break;
647             /* Full text transparency. only foreground color is show */
648             case VBI_TRANSPARENT_FULL:
649                 if( (*p_pixel) == (0xff000000 | p_page->color_map[background] ) )
650                     *p_pixel = 0;
651                 break;
652             }
653         }
654     }
655     /* end of kludge */
656     return VLC_SUCCESS;
657 }
658
659 /* Callbacks */
660 static int RequestPage( vlc_object_t *p_this, char const *psz_cmd,
661                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
662 {
663     decoder_sys_t *p_sys = p_data;
664     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
665
666     vlc_mutex_lock( &p_sys->lock );
667     switch( newval.i_int )
668     {
669         case ZVBI_KEY_RED:
670             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[0].pgno );
671             p_sys->i_wanted_subpage = p_sys->nav_link[0].subno;
672             break;
673         case ZVBI_KEY_GREEN:
674             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[1].pgno );
675             p_sys->i_wanted_subpage = p_sys->nav_link[1].subno;
676             break;
677         case ZVBI_KEY_YELLOW:
678             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[2].pgno );
679             p_sys->i_wanted_subpage = p_sys->nav_link[2].subno;
680             break;
681         case ZVBI_KEY_BLUE:
682             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[3].pgno );
683             p_sys->i_wanted_subpage = p_sys->nav_link[3].subno;
684             break;
685         case ZVBI_KEY_INDEX:
686             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[5].pgno ); /* #4 is SKIPPED */
687             p_sys->i_wanted_subpage = p_sys->nav_link[5].subno;
688             break;
689     }
690     if( newval.i_int > 0 && newval.i_int < 999 )
691     {
692         p_sys->i_wanted_page = newval.i_int;
693         p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
694     }
695     vlc_mutex_unlock( &p_sys->lock );
696
697     return VLC_SUCCESS;
698 }
699
700 static int Opaque( vlc_object_t *p_this, char const *psz_cmd,
701                    vlc_value_t oldval, vlc_value_t newval, void *p_data )
702 {
703     decoder_sys_t *p_sys = p_data;
704     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
705
706     vlc_mutex_lock( &p_sys->lock );
707     p_sys->b_opaque = newval.b_bool;
708     p_sys->b_update = true;
709     vlc_mutex_unlock( &p_sys->lock );
710
711     return VLC_SUCCESS;
712 }
713
714 static int Position( vlc_object_t *p_this, char const *psz_cmd,
715                      vlc_value_t oldval, vlc_value_t newval, void *p_data )
716 {
717     decoder_sys_t *p_sys = p_data;
718     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
719
720     vlc_mutex_lock( &p_sys->lock );
721     p_sys->i_align = newval.i_int;
722     vlc_mutex_unlock( &p_sys->lock );
723
724     return VLC_SUCCESS;
725 }
726
727 static int EventKey( vlc_object_t *p_this, char const *psz_cmd,
728                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
729 {
730     decoder_t *p_dec = p_data;
731     decoder_sys_t *p_sys = p_dec->p_sys;
732
733     VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED( p_this );
734
735     /* FIXME: Capture + and - key for subpage browsing */
736     if( newval.i_int == '-' || newval.i_int == '+' )
737     {
738         vlc_mutex_lock( &p_sys->lock );
739         if( p_sys->i_wanted_subpage == VBI_ANY_SUBNO && newval.i_int == '+' )
740             p_sys->i_wanted_subpage = vbi_dec2bcd(1);
741         else if ( newval.i_int == '+' )
742             p_sys->i_wanted_subpage = vbi_add_bcd( p_sys->i_wanted_subpage, 1);
743         else if( newval.i_int == '-')
744             p_sys->i_wanted_subpage = vbi_add_bcd( p_sys->i_wanted_subpage, 0xF9999999); /* BCD complement - 1 */
745
746         if ( !vbi_bcd_digits_greater( p_sys->i_wanted_subpage, 0x00 ) || vbi_bcd_digits_greater( p_sys->i_wanted_subpage, 0x99 ) )
747                 p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
748         else
749             msg_Info( p_dec, "subpage: %d",
750                       vbi_bcd2dec( p_sys->i_wanted_subpage) );
751
752         p_sys->b_update = true;
753         vlc_mutex_unlock( &p_sys->lock );
754     }
755
756     /* Capture 0-9 for page selection */
757     if( newval.i_int < '0' || newval.i_int > '9' )
758         return VLC_SUCCESS;
759
760     vlc_mutex_lock( &p_sys->lock );
761     p_sys->i_key[0] = p_sys->i_key[1];
762     p_sys->i_key[1] = p_sys->i_key[2];
763     p_sys->i_key[2] = (int)(newval.i_int - '0');
764     msg_Info( p_dec, "page: %c%c%c", (char)(p_sys->i_key[0]+'0'),
765               (char)(p_sys->i_key[1]+'0'), (char)(p_sys->i_key[2]+'0') );
766
767     int i_new_page = 0;
768
769     if( p_sys->i_key[0] > 0 && p_sys->i_key[0] <= 8 &&
770         p_sys->i_key[1] >= 0 && p_sys->i_key[1] <= 9 &&
771         p_sys->i_key[2] >= 0 && p_sys->i_key[2] <= 9 )
772     {
773         i_new_page = p_sys->i_key[0]*100 + p_sys->i_key[1]*10 + p_sys->i_key[2];
774         p_sys->i_key[0] = p_sys->i_key[1] = p_sys->i_key[2] = '*' - '0';
775     }
776     vlc_mutex_unlock( &p_sys->lock );
777
778     if( i_new_page > 0 )
779         var_SetInteger( p_dec, "vbi-page", i_new_page );
780
781     return VLC_SUCCESS;
782 }