]> git.sesse.net Git - vlc/blob - modules/codec/zvbi.c
mediacodec: fix warning
[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                 if(p_block->p_buffer[3] == 0xE4 )    /* framing_code */
335                 {
336                     unsigned line_offset  = p_block->p_buffer[2] & 0x1f;
337                     unsigned field_parity = p_block->p_buffer[2] & 0x20;
338
339                     p_sliced[i_lines].id = VBI_SLICED_TELETEXT_B;
340                     if( line_offset > 0 )
341                         p_sliced[i_lines].line = line_offset + (field_parity ? 0 : 313);
342                     else
343                         p_sliced[i_lines].line = 0;
344                     for( int i = 0; i < 42; i++ )
345                         p_sliced[i_lines].data[i] = vbi_rev8( p_block->p_buffer[4 + i] );
346                     i_lines++;
347                 }
348             }
349
350             p_block->i_buffer -= 2 + i_size;
351             p_block->p_buffer += 2 + i_size;
352         }
353
354         if( i_lines > 0 )
355             vbi_decode( p_sys->p_vbi_dec, p_sliced, i_lines, 0 );
356     }
357
358     /* */
359     vlc_mutex_lock( &p_sys->lock );
360     const int i_align = p_sys->i_align;
361     const unsigned int i_wanted_page = p_sys->i_wanted_page;
362     const unsigned int i_wanted_subpage = p_sys->i_wanted_subpage;
363     const bool b_opaque = p_sys->b_opaque;
364     vlc_mutex_unlock( &p_sys->lock );
365
366     /* Try to see if the page we want is in the cache yet */
367     memset( &p_page, 0, sizeof(vbi_page) );
368     b_cached = vbi_fetch_vt_page( p_sys->p_vbi_dec, &p_page,
369                                   vbi_dec2bcd( i_wanted_page ),
370                                   i_wanted_subpage, VBI_WST_LEVEL_3p5,
371                                   25, true );
372
373     if( i_wanted_page == p_sys->i_last_page && !p_sys->b_update )
374         goto error;
375
376     if( !b_cached )
377     {
378         if( p_sys->b_text && p_sys->i_last_page != i_wanted_page )
379         {
380             /* We need to reset the subtitle */
381             p_spu = Subpicture( p_dec, &fmt, true,
382                                 p_page.columns, p_page.rows,
383                                 i_align, p_block->i_pts );
384             if( !p_spu )
385                 goto error;
386             subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
387             p_spu_sys->text = strdup("");
388
389             p_sys->b_update = true;
390             p_sys->i_last_page = i_wanted_page;
391             goto exit;
392         }
393         goto error;
394     }
395
396     p_sys->b_update = false;
397     p_sys->i_last_page = i_wanted_page;
398 #ifdef ZVBI_DEBUG
399     msg_Dbg( p_dec, "we now have page: %d ready for display",
400              i_wanted_page );
401 #endif
402
403     /* Ignore transparent rows at the beginning and end */
404     int i_first_row = get_first_visible_row( p_page.text, p_page.rows, p_page.columns );
405     int i_num_rows;
406     if ( i_first_row < 0 ) {
407         i_first_row = p_page.rows - 1;
408         i_num_rows = 0;
409     } else {
410         i_num_rows = get_last_visible_row( p_page.text, p_page.rows, p_page.columns ) - i_first_row + 1;
411     }
412 #ifdef ZVBI_DEBUG
413     msg_Dbg( p_dec, "After top and tail of page we have rows %i-%i of %i",
414              i_first_row + 1, i_first_row + i_num_rows, p_page.rows );
415 #endif
416
417     /* If there is a page or sub to render, then we do that here */
418     /* Create the subpicture unit */
419     p_spu = Subpicture( p_dec, &fmt, p_sys->b_text,
420                         p_page.columns, i_num_rows,
421                         i_align, p_block->i_pts );
422     if( !p_spu )
423         goto error;
424
425     if( p_sys->b_text )
426     {
427         unsigned int i_textsize = 7000;
428         int i_total,offset;
429         char p_text[i_textsize+1];
430
431         i_total = vbi_print_page_region( &p_page, p_text, i_textsize,
432                         "UTF-8", 0, 0, 0, i_first_row, p_page.columns, i_num_rows );
433
434         for( offset=1; offset<i_total && isspace( p_text[i_total-offset ] ); offset++)
435            p_text[i_total-offset] = '\0';
436
437         i_total -= offset;
438
439         offset=0;
440         while( offset < i_total && isspace( p_text[offset] ) )
441            offset++;
442
443         subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
444         p_spu_sys->text = strdup( &p_text[offset] );
445
446         p_spu_sys->align = i_align;
447         p_spu_sys->i_font_height_percent = 5;
448         p_spu_sys->renderbg = b_opaque;
449
450 #ifdef ZVBI_DEBUG
451         msg_Info( p_dec, "page %x-%x(%d)\n\"%s\"", p_page.pgno, p_page.subno, i_total, &p_text[offset] );
452 #endif
453     }
454     else
455     {
456         picture_t *p_pic = p_spu->p_region->p_picture;
457
458         /* ZVBI is stupid enough to assume pitch == width */
459         p_pic->p->i_pitch = 4 * fmt.i_width;
460
461         /* Maintain subtitle postion */
462         p_spu->p_region->i_y = i_first_row*10;
463         p_spu->i_original_picture_width = p_page.columns*12;
464         p_spu->i_original_picture_height = p_page.rows*10;
465
466         vbi_draw_vt_page_region( &p_page, ZVBI_PIXFMT_RGBA32,
467                           p_spu->p_region->p_picture->p->p_pixels, -1,
468                           0, i_first_row, p_page.columns, i_num_rows,
469                           1, 1);
470
471         vlc_mutex_lock( &p_sys->lock );
472         memcpy( p_sys->nav_link, &p_page.nav_link, sizeof( p_sys->nav_link )) ;
473         vlc_mutex_unlock( &p_sys->lock );
474
475         OpaquePage( p_pic, &p_page, fmt, b_opaque, i_first_row * p_page.columns );
476     }
477
478 exit:
479     vbi_unref_page( &p_page );
480     block_Release( p_block );
481     return p_spu;
482
483 error:
484     vbi_unref_page( &p_page );
485     block_Release( p_block );
486     return NULL;
487 }
488
489 static subpicture_t *Subpicture( decoder_t *p_dec, video_format_t *p_fmt,
490                                  bool b_text,
491                                  int i_columns, int i_rows, int i_align,
492                                  mtime_t i_pts )
493 {
494     video_format_t fmt;
495     subpicture_t *p_spu=NULL;
496
497     /* If there is a page or sub to render, then we do that here */
498     /* Create the subpicture unit */
499     if( b_text )
500         p_spu = decoder_NewSubpictureText( p_dec );
501     else
502         p_spu = decoder_NewSubpicture( p_dec, NULL );
503     if( !p_spu )
504     {
505         msg_Warn( p_dec, "can't get spu buffer" );
506         return NULL;
507     }
508
509     video_format_Init(&fmt, b_text ? VLC_CODEC_TEXT : VLC_CODEC_RGBA);
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         fmt.i_sar_num = fmt.i_sar_den = 0; /* let the vout set the correct AR */
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         subpicture_Delete( 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 = b_text ? i_pts + (10*CLOCK_FREQ): 0;
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 -1;
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;
610         }
611     }
612
613     return -1;
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     int want_navlink = -1;
666
667     vlc_mutex_lock( &p_sys->lock );
668     switch( newval.i_int )
669     {
670         case ZVBI_KEY_RED:
671             want_navlink = 0;
672             break;
673         case ZVBI_KEY_GREEN:
674             want_navlink = 1;
675             break;
676         case ZVBI_KEY_YELLOW:
677             want_navlink = 2;
678             break;
679         case ZVBI_KEY_BLUE:
680             want_navlink = 3;
681             break;
682         case ZVBI_KEY_INDEX:
683             want_navlink = 5; /* #4 is SKIPPED */
684             break;
685     }
686
687     if (want_navlink > -1)
688     {
689         int page = vbi_bcd2dec( p_sys->nav_link[want_navlink].pgno );
690         if (page > 0 && page < 999) {
691             p_sys->i_wanted_page = page;
692             p_sys->i_wanted_subpage = p_sys->nav_link[want_navlink].subno;
693         }
694     }
695     else if( newval.i_int > 0 && newval.i_int < 999 )
696     {
697         p_sys->i_wanted_page = newval.i_int;
698         p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
699     }
700     vlc_mutex_unlock( &p_sys->lock );
701
702     return VLC_SUCCESS;
703 }
704
705 static int Opaque( vlc_object_t *p_this, char const *psz_cmd,
706                    vlc_value_t oldval, vlc_value_t newval, void *p_data )
707 {
708     decoder_sys_t *p_sys = p_data;
709     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
710
711     vlc_mutex_lock( &p_sys->lock );
712     p_sys->b_opaque = newval.b_bool;
713     p_sys->b_update = true;
714     vlc_mutex_unlock( &p_sys->lock );
715
716     return VLC_SUCCESS;
717 }
718
719 static int Position( vlc_object_t *p_this, char const *psz_cmd,
720                      vlc_value_t oldval, vlc_value_t newval, void *p_data )
721 {
722     decoder_sys_t *p_sys = p_data;
723     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
724
725     vlc_mutex_lock( &p_sys->lock );
726     p_sys->i_align = newval.i_int;
727     vlc_mutex_unlock( &p_sys->lock );
728
729     return VLC_SUCCESS;
730 }
731
732 static int EventKey( vlc_object_t *p_this, char const *psz_cmd,
733                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
734 {
735     decoder_t *p_dec = p_data;
736     decoder_sys_t *p_sys = p_dec->p_sys;
737
738     VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED( p_this );
739
740     /* FIXME: Capture + and - key for subpage browsing */
741     if( newval.i_int == '-' || newval.i_int == '+' )
742     {
743         vlc_mutex_lock( &p_sys->lock );
744         if( p_sys->i_wanted_subpage == VBI_ANY_SUBNO && newval.i_int == '+' )
745             p_sys->i_wanted_subpage = vbi_dec2bcd(1);
746         else if ( newval.i_int == '+' )
747             p_sys->i_wanted_subpage = vbi_add_bcd( p_sys->i_wanted_subpage, 1);
748         else if( newval.i_int == '-')
749             p_sys->i_wanted_subpage = vbi_add_bcd( p_sys->i_wanted_subpage, 0xF9999999); /* BCD complement - 1 */
750
751         if ( !vbi_bcd_digits_greater( p_sys->i_wanted_subpage, 0x00 ) || vbi_bcd_digits_greater( p_sys->i_wanted_subpage, 0x99 ) )
752                 p_sys->i_wanted_subpage = VBI_ANY_SUBNO;
753         else
754             msg_Info( p_dec, "subpage: %d",
755                       vbi_bcd2dec( p_sys->i_wanted_subpage) );
756
757         p_sys->b_update = true;
758         vlc_mutex_unlock( &p_sys->lock );
759     }
760
761     /* Capture 0-9 for page selection */
762     if( newval.i_int < '0' || newval.i_int > '9' )
763         return VLC_SUCCESS;
764
765     vlc_mutex_lock( &p_sys->lock );
766     p_sys->i_key[0] = p_sys->i_key[1];
767     p_sys->i_key[1] = p_sys->i_key[2];
768     p_sys->i_key[2] = (int)(newval.i_int - '0');
769     msg_Info( p_dec, "page: %c%c%c", (char)(p_sys->i_key[0]+'0'),
770               (char)(p_sys->i_key[1]+'0'), (char)(p_sys->i_key[2]+'0') );
771
772     int i_new_page = 0;
773
774     if( p_sys->i_key[0] > 0 && p_sys->i_key[0] <= 8 &&
775         p_sys->i_key[1] >= 0 && p_sys->i_key[1] <= 9 &&
776         p_sys->i_key[2] >= 0 && p_sys->i_key[2] <= 9 )
777     {
778         i_new_page = p_sys->i_key[0]*100 + p_sys->i_key[1]*10 + p_sys->i_key[2];
779         p_sys->i_key[0] = p_sys->i_key[1] = p_sys->i_key[2] = '*' - '0';
780     }
781     vlc_mutex_unlock( &p_sys->lock );
782
783     if( i_new_page > 0 )
784         var_SetInteger( p_dec, "vbi-page", i_new_page );
785
786     return VLC_SUCCESS;
787 }