]> git.sesse.net Git - vlc/blob - modules/codec/zvbi.c
zvbi: dead code
[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 true " \
67         "makes the text to be boxed and maybe easier to read." )
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", false,
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     int i_num_rows;
403     if ( i_first_row < 0 ) {
404         i_first_row = p_page.rows - 1;
405         i_num_rows = 0;
406     } else {
407         i_num_rows = get_last_visible_row( p_page.text, p_page.rows, p_page.columns ) - i_first_row + 1;
408     }
409 #ifdef ZVBI_DEBUG
410     msg_Dbg( p_dec, "After top and tail of page we have rows %i-%i of %i",
411              i_first_row + 1, i_first_row + i_num_rows, p_page.rows );
412 #endif
413
414     /* If there is a page or sub to render, then we do that here */
415     /* Create the subpicture unit */
416     p_spu = Subpicture( p_dec, &fmt, p_sys->b_text,
417                         p_page.columns, i_num_rows,
418                         i_align, p_block->i_pts );
419     if( !p_spu )
420         goto error;
421
422     if( p_sys->b_text )
423     {
424         unsigned int i_textsize = 7000;
425         int i_total,offset;
426         char p_text[i_textsize+1];
427
428         i_total = vbi_print_page_region( &p_page, p_text, i_textsize,
429                         "UTF-8", 0, 0, 0, i_first_row, p_page.columns, i_num_rows );
430
431         for( offset=1; offset<i_total && isspace( p_text[i_total-offset ] ); offset++)
432            p_text[i_total-offset] = '\0';
433
434         i_total -= offset;
435
436         offset=0;
437         while( offset < i_total && isspace( p_text[offset] ) )
438            offset++;
439
440         subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
441         p_spu_sys->text = strdup( &p_text[offset] );
442
443         p_spu_sys->align = i_align;
444         p_spu_sys->i_font_height_percent = 5;
445         p_spu_sys->renderbg = b_opaque;
446
447 #ifdef ZVBI_DEBUG
448         msg_Info( p_dec, "page %x-%x(%d)\n\"%s\"", p_page.pgno, p_page.subno, i_total, &p_text[offset] );
449 #endif
450     }
451     else
452     {
453         picture_t *p_pic = p_spu->p_region->p_picture;
454
455         /* ZVBI is stupid enough to assume pitch == width */
456         p_pic->p->i_pitch = 4 * fmt.i_width;
457
458         /* Maintain subtitle postion */
459         p_spu->p_region->i_y = i_first_row*10;
460         p_spu->i_original_picture_width = p_page.columns*12;
461         p_spu->i_original_picture_height = p_page.rows*10;
462
463         vbi_draw_vt_page_region( &p_page, ZVBI_PIXFMT_RGBA32,
464                           p_spu->p_region->p_picture->p->p_pixels, -1,
465                           0, i_first_row, p_page.columns, i_num_rows,
466                           1, 1);
467
468         vlc_mutex_lock( &p_sys->lock );
469         memcpy( p_sys->nav_link, &p_page.nav_link, sizeof( p_sys->nav_link )) ;
470         vlc_mutex_unlock( &p_sys->lock );
471
472         OpaquePage( p_pic, &p_page, fmt, b_opaque, i_first_row * p_page.columns );
473     }
474
475 exit:
476     vbi_unref_page( &p_page );
477     block_Release( p_block );
478     return p_spu;
479
480 error:
481     vbi_unref_page( &p_page );
482     block_Release( p_block );
483     return NULL;
484 }
485
486 static subpicture_t *Subpicture( decoder_t *p_dec, video_format_t *p_fmt,
487                                  bool b_text,
488                                  int i_columns, int i_rows, int i_align,
489                                  mtime_t i_pts )
490 {
491     video_format_t fmt;
492     subpicture_t *p_spu=NULL;
493
494     /* If there is a page or sub to render, then we do that here */
495     /* Create the subpicture unit */
496     if( b_text )
497         p_spu = decoder_NewSubpictureText( p_dec );
498     else
499         p_spu = decoder_NewSubpicture( p_dec, NULL );
500     if( !p_spu )
501     {
502         msg_Warn( p_dec, "can't get spu buffer" );
503         return NULL;
504     }
505
506     memset( &fmt, 0, sizeof(video_format_t) );
507     fmt.i_chroma = b_text ? VLC_CODEC_TEXT : VLC_CODEC_RGBA;
508     fmt.i_sar_num = 0;
509     fmt.i_sar_den = 1;
510     if( b_text )
511     {
512         fmt.i_bits_per_pixel = 0;
513     }
514     else
515     {
516         fmt.i_width = fmt.i_visible_width = i_columns * 12;
517         fmt.i_height = fmt.i_visible_height = i_rows * 10;
518         fmt.i_bits_per_pixel = 32;
519     }
520     fmt.i_x_offset = fmt.i_y_offset = 0;
521
522     p_spu->p_region = subpicture_region_New( &fmt );
523     if( p_spu->p_region == NULL )
524     {
525         msg_Err( p_dec, "cannot allocate SPU region" );
526         decoder_DeleteSubpicture( p_dec, p_spu );
527         return NULL;
528     }
529
530     p_spu->p_region->i_x = 0;
531     p_spu->p_region->i_y = 0;
532
533     p_spu->i_start = i_pts;
534     p_spu->i_stop = b_text ? i_pts + (10*CLOCK_FREQ): 0;
535     p_spu->b_ephemer = true;
536     p_spu->b_absolute = b_text ? false : true;
537
538     if( !b_text )
539         p_spu->p_region->i_align = i_align;
540     p_spu->i_original_picture_width = fmt.i_width;
541     p_spu->i_original_picture_height = fmt.i_height;
542
543     /* */
544     *p_fmt = fmt;
545     return p_spu;
546 }
547
548 static void EventHandler( vbi_event *ev, void *user_data )
549 {
550     decoder_t *p_dec        = (decoder_t *)user_data;
551     decoder_sys_t *p_sys    = p_dec->p_sys;
552
553     if( ev->type == VBI_EVENT_TTX_PAGE )
554     {
555 #ifdef ZVBI_DEBUG
556         msg_Info( p_dec, "Page %03x.%02x ",
557                     ev->ev.ttx_page.pgno,
558                     ev->ev.ttx_page.subno & 0xFF);
559 #endif
560         if( p_sys->i_last_page == vbi_bcd2dec( ev->ev.ttx_page.pgno ) )
561             p_sys->b_update = true;
562 #ifdef ZVBI_DEBUG
563         if( ev->ev.ttx_page.clock_update )
564             msg_Dbg( p_dec, "clock" );
565         if( ev->ev.ttx_page.header_update )
566             msg_Dbg( p_dec, "header" );
567 #endif
568     }
569     else if( ev->type == VBI_EVENT_CLOSE )
570         msg_Dbg( p_dec, "Close event" );
571     else if( ev->type == VBI_EVENT_CAPTION )
572         msg_Dbg( p_dec, "Caption line: %x", ev->ev.caption.pgno );
573     else if( ev->type == VBI_EVENT_NETWORK )
574     {
575         msg_Dbg( p_dec, "Network change");
576         vbi_network n = ev->ev.network;
577         msg_Dbg( p_dec, "Network id:%d name: %s, call: %s ", n.nuid, n.name, n.call );
578     }
579     else if( ev->type == VBI_EVENT_TRIGGER )
580         msg_Dbg( p_dec, "Trigger event" );
581     else if( ev->type == VBI_EVENT_ASPECT )
582         msg_Dbg( p_dec, "Aspect update" );
583     else if( ev->type == VBI_EVENT_PROG_INFO )
584         msg_Dbg( p_dec, "Program info received" );
585     else if( ev->type == VBI_EVENT_NETWORK_ID )
586         msg_Dbg( p_dec, "Network ID changed" );
587 }
588
589 static int get_first_visible_row( vbi_char *p_text, int rows, int columns)
590 {
591     for ( int i = 0; i < rows * columns; i++ )
592     {
593         if ( p_text[i].opacity != VBI_TRANSPARENT_SPACE )
594         {
595             return i / columns;
596         }
597     }
598
599     return -1;
600 }
601
602 static int get_last_visible_row( vbi_char *p_text, int rows, int columns)
603 {
604     for ( int i = rows * columns - 1; i >= 0; i-- )
605     {
606         if (p_text[i].opacity != VBI_TRANSPARENT_SPACE)
607         {
608             return ( i + columns - 1) / columns;
609         }
610     }
611
612     return -1;
613 }
614
615 static int OpaquePage( picture_t *p_src, const vbi_page *p_page,
616                        const video_format_t fmt, bool b_opaque, const int text_offset )
617 {
618     unsigned int    x, y;
619
620     assert( fmt.i_chroma == VLC_CODEC_RGBA );
621
622     /* Kludge since zvbi doesn't provide an option to specify opacity. */
623     for( y = 0; y < fmt.i_height; y++ )
624     {
625         for( x = 0; x < fmt.i_width; x++ )
626         {
627             const vbi_opacity opacity = p_page->text[ text_offset + y/10 * p_page->columns + x/12 ].opacity;
628             const int background = p_page->text[ text_offset + y/10 * p_page->columns + x/12 ].background;
629             uint32_t *p_pixel = (uint32_t*)&p_src->p->p_pixels[y * p_src->p->i_pitch + 4*x];
630
631             switch( opacity )
632             {
633             /* Show video instead of this character */
634             case VBI_TRANSPARENT_SPACE:
635                 *p_pixel = 0;
636                 break;
637             /* Display foreground and background color */
638             /* To make the boxed text "closed captioning" transparent
639              * change true to false.
640              */
641             case VBI_OPAQUE:
642             /* alpha blend video into background color */
643             case VBI_SEMI_TRANSPARENT:
644                 if( b_opaque )
645                     break;
646             /* Full text transparency. only foreground color is show */
647             case VBI_TRANSPARENT_FULL:
648                 if( (*p_pixel) == (0xff000000 | p_page->color_map[background] ) )
649                     *p_pixel = 0;
650                 break;
651             }
652         }
653     }
654     /* end of kludge */
655     return VLC_SUCCESS;
656 }
657
658 /* Callbacks */
659 static int RequestPage( vlc_object_t *p_this, char const *psz_cmd,
660                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
661 {
662     decoder_sys_t *p_sys = p_data;
663     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
664
665     vlc_mutex_lock( &p_sys->lock );
666     switch( newval.i_int )
667     {
668         case ZVBI_KEY_RED:
669             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[0].pgno );
670             p_sys->i_wanted_subpage = p_sys->nav_link[0].subno;
671             break;
672         case ZVBI_KEY_GREEN:
673             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[1].pgno );
674             p_sys->i_wanted_subpage = p_sys->nav_link[1].subno;
675             break;
676         case ZVBI_KEY_YELLOW:
677             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[2].pgno );
678             p_sys->i_wanted_subpage = p_sys->nav_link[2].subno;
679             break;
680         case ZVBI_KEY_BLUE:
681             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[3].pgno );
682             p_sys->i_wanted_subpage = p_sys->nav_link[3].subno;
683             break;
684         case ZVBI_KEY_INDEX:
685             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[5].pgno ); /* #4 is SKIPPED */
686             p_sys->i_wanted_subpage = p_sys->nav_link[5].subno;
687             break;
688     }
689     if( newval.i_int > 0 && newval.i_int < 999 )
690     {
691         p_sys->i_wanted_page = newval.i_int;
692         p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
693     }
694     vlc_mutex_unlock( &p_sys->lock );
695
696     return VLC_SUCCESS;
697 }
698
699 static int Opaque( vlc_object_t *p_this, char const *psz_cmd,
700                    vlc_value_t oldval, vlc_value_t newval, void *p_data )
701 {
702     decoder_sys_t *p_sys = p_data;
703     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
704
705     vlc_mutex_lock( &p_sys->lock );
706     p_sys->b_opaque = newval.b_bool;
707     p_sys->b_update = true;
708     vlc_mutex_unlock( &p_sys->lock );
709
710     return VLC_SUCCESS;
711 }
712
713 static int Position( vlc_object_t *p_this, char const *psz_cmd,
714                      vlc_value_t oldval, vlc_value_t newval, void *p_data )
715 {
716     decoder_sys_t *p_sys = p_data;
717     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
718
719     vlc_mutex_lock( &p_sys->lock );
720     p_sys->i_align = newval.i_int;
721     vlc_mutex_unlock( &p_sys->lock );
722
723     return VLC_SUCCESS;
724 }
725
726 static int EventKey( vlc_object_t *p_this, char const *psz_cmd,
727                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
728 {
729     decoder_t *p_dec = p_data;
730     decoder_sys_t *p_sys = p_dec->p_sys;
731
732     VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED( p_this );
733
734     /* FIXME: Capture + and - key for subpage browsing */
735     if( newval.i_int == '-' || newval.i_int == '+' )
736     {
737         vlc_mutex_lock( &p_sys->lock );
738         if( p_sys->i_wanted_subpage == VBI_ANY_SUBNO && newval.i_int == '+' )
739             p_sys->i_wanted_subpage = vbi_dec2bcd(1);
740         else if ( newval.i_int == '+' )
741             p_sys->i_wanted_subpage = vbi_add_bcd( p_sys->i_wanted_subpage, 1);
742         else if( newval.i_int == '-')
743             p_sys->i_wanted_subpage = vbi_add_bcd( p_sys->i_wanted_subpage, 0xF9999999); /* BCD complement - 1 */
744
745         if ( !vbi_bcd_digits_greater( p_sys->i_wanted_subpage, 0x00 ) || vbi_bcd_digits_greater( p_sys->i_wanted_subpage, 0x99 ) )
746                 p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
747         else
748             msg_Info( p_dec, "subpage: %d",
749                       vbi_bcd2dec( p_sys->i_wanted_subpage) );
750
751         p_sys->b_update = true;
752         vlc_mutex_unlock( &p_sys->lock );
753     }
754
755     /* Capture 0-9 for page selection */
756     if( newval.i_int < '0' || newval.i_int > '9' )
757         return VLC_SUCCESS;
758
759     vlc_mutex_lock( &p_sys->lock );
760     p_sys->i_key[0] = p_sys->i_key[1];
761     p_sys->i_key[1] = p_sys->i_key[2];
762     p_sys->i_key[2] = (int)(newval.i_int - '0');
763     msg_Info( p_dec, "page: %c%c%c", (char)(p_sys->i_key[0]+'0'),
764               (char)(p_sys->i_key[1]+'0'), (char)(p_sys->i_key[2]+'0') );
765
766     int i_new_page = 0;
767
768     if( p_sys->i_key[0] > 0 && p_sys->i_key[0] <= 8 &&
769         p_sys->i_key[1] >= 0 && p_sys->i_key[1] <= 9 &&
770         p_sys->i_key[2] >= 0 && p_sys->i_key[2] <= 9 )
771     {
772         i_new_page = p_sys->i_key[0]*100 + p_sys->i_key[1]*10 + p_sys->i_key[2];
773         p_sys->i_key[0] = p_sys->i_key[1] = p_sys->i_key[2] = '*' - '0';
774     }
775     vlc_mutex_unlock( &p_sys->lock );
776
777     if( i_new_page > 0 )
778         var_SetInteger( p_dec, "vbi-page", i_new_page );
779
780     return VLC_SUCCESS;
781 }