]> git.sesse.net Git - vlc/blob - modules/codec/zvbi.c
Correctly process wholly translucent zvbi 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 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     if( p_spu != NULL )
483     {
484         decoder_DeleteSubpicture( p_dec, p_spu );
485         p_spu = NULL;
486     }
487
488     block_Release( p_block );
489     return NULL;
490 }
491
492 static subpicture_t *Subpicture( decoder_t *p_dec, video_format_t *p_fmt,
493                                  bool b_text,
494                                  int i_columns, int i_rows, int i_align,
495                                  mtime_t i_pts )
496 {
497     video_format_t fmt;
498     subpicture_t *p_spu=NULL;
499
500     /* If there is a page or sub to render, then we do that here */
501     /* Create the subpicture unit */
502     if( b_text )
503         p_spu = decoder_NewSubpictureText( p_dec );
504     else
505         p_spu = decoder_NewSubpicture( p_dec, NULL );
506     if( !p_spu )
507     {
508         msg_Warn( p_dec, "can't get spu buffer" );
509         return NULL;
510     }
511
512     memset( &fmt, 0, sizeof(video_format_t) );
513     fmt.i_chroma = b_text ? VLC_CODEC_TEXT : VLC_CODEC_RGBA;
514     fmt.i_sar_num = 0;
515     fmt.i_sar_den = 1;
516     if( b_text )
517     {
518         fmt.i_bits_per_pixel = 0;
519     }
520     else
521     {
522         fmt.i_width = fmt.i_visible_width = i_columns * 12;
523         fmt.i_height = fmt.i_visible_height = i_rows * 10;
524         fmt.i_bits_per_pixel = 32;
525     }
526     fmt.i_x_offset = fmt.i_y_offset = 0;
527
528     p_spu->p_region = subpicture_region_New( &fmt );
529     if( p_spu->p_region == NULL )
530     {
531         msg_Err( p_dec, "cannot allocate SPU region" );
532         decoder_DeleteSubpicture( p_dec, p_spu );
533         return NULL;
534     }
535
536     p_spu->p_region->i_x = 0;
537     p_spu->p_region->i_y = 0;
538
539     p_spu->i_start = i_pts;
540     p_spu->i_stop = b_text ? i_pts + (10*CLOCK_FREQ): 0;
541     p_spu->b_ephemer = true;
542     p_spu->b_absolute = b_text ? false : true;
543
544     if( !b_text )
545         p_spu->p_region->i_align = i_align;
546     p_spu->i_original_picture_width = fmt.i_width;
547     p_spu->i_original_picture_height = fmt.i_height;
548
549     /* */
550     *p_fmt = fmt;
551     return p_spu;
552 }
553
554 static void EventHandler( vbi_event *ev, void *user_data )
555 {
556     decoder_t *p_dec        = (decoder_t *)user_data;
557     decoder_sys_t *p_sys    = p_dec->p_sys;
558
559     if( ev->type == VBI_EVENT_TTX_PAGE )
560     {
561 #ifdef ZVBI_DEBUG
562         msg_Info( p_dec, "Page %03x.%02x ",
563                     ev->ev.ttx_page.pgno,
564                     ev->ev.ttx_page.subno & 0xFF);
565 #endif
566         if( p_sys->i_last_page == vbi_bcd2dec( ev->ev.ttx_page.pgno ) )
567             p_sys->b_update = true;
568 #ifdef ZVBI_DEBUG
569         if( ev->ev.ttx_page.clock_update )
570             msg_Dbg( p_dec, "clock" );
571         if( ev->ev.ttx_page.header_update )
572             msg_Dbg( p_dec, "header" );
573 #endif
574     }
575     else if( ev->type == VBI_EVENT_CLOSE )
576         msg_Dbg( p_dec, "Close event" );
577     else if( ev->type == VBI_EVENT_CAPTION )
578         msg_Dbg( p_dec, "Caption line: %x", ev->ev.caption.pgno );
579     else if( ev->type == VBI_EVENT_NETWORK )
580     {
581         msg_Dbg( p_dec, "Network change");
582         vbi_network n = ev->ev.network;
583         msg_Dbg( p_dec, "Network id:%d name: %s, call: %s ", n.nuid, n.name, n.call );
584     }
585     else if( ev->type == VBI_EVENT_TRIGGER )
586         msg_Dbg( p_dec, "Trigger event" );
587     else if( ev->type == VBI_EVENT_ASPECT )
588         msg_Dbg( p_dec, "Aspect update" );
589     else if( ev->type == VBI_EVENT_PROG_INFO )
590         msg_Dbg( p_dec, "Program info received" );
591     else if( ev->type == VBI_EVENT_NETWORK_ID )
592         msg_Dbg( p_dec, "Network ID changed" );
593 }
594
595 static int get_first_visible_row( vbi_char *p_text, int rows, int columns)
596 {
597     for ( int i = 0; i < rows * columns; i++ )
598     {
599         if ( p_text[i].opacity != VBI_TRANSPARENT_SPACE )
600         {
601             return i / columns;
602         }
603     }
604
605     return -1;
606 }
607
608 static int get_last_visible_row( vbi_char *p_text, int rows, int columns)
609 {
610     for ( int i = rows * columns - 1; i >= 0; i-- )
611     {
612         if (p_text[i].opacity != VBI_TRANSPARENT_SPACE)
613         {
614             return ( i + columns - 1) / columns;
615         }
616     }
617
618     return -1;
619 }
620
621 static int OpaquePage( picture_t *p_src, const vbi_page p_page,
622                        const video_format_t fmt, bool b_opaque, const int text_offset )
623 {
624     unsigned int    x, y;
625
626     assert( fmt.i_chroma == VLC_CODEC_RGBA );
627
628     /* Kludge since zvbi doesn't provide an option to specify opacity. */
629     for( y = 0; y < fmt.i_height; y++ )
630     {
631         for( x = 0; x < fmt.i_width; x++ )
632         {
633             const vbi_opacity opacity = p_page.text[ text_offset + y/10 * p_page.columns + x/12 ].opacity;
634             const int background = p_page.text[ text_offset + y/10 * p_page.columns + x/12 ].background;
635             uint32_t *p_pixel = (uint32_t*)&p_src->p->p_pixels[y * p_src->p->i_pitch + 4*x];
636
637             switch( opacity )
638             {
639             /* Show video instead of this character */
640             case VBI_TRANSPARENT_SPACE:
641                 *p_pixel = 0;
642                 break;
643             /* Display foreground and background color */
644             /* To make the boxed text "closed captioning" transparent
645              * change true to false.
646              */
647             case VBI_OPAQUE:
648             /* alpha blend video into background color */
649             case VBI_SEMI_TRANSPARENT:
650                 if( b_opaque )
651                     break;
652             /* Full text transparency. only foreground color is show */
653             case VBI_TRANSPARENT_FULL:
654                 if( (*p_pixel) == (0xff000000 | p_page.color_map[background] ) )
655                     *p_pixel = 0;
656                 break;
657             }
658         }
659     }
660     /* end of kludge */
661     return VLC_SUCCESS;
662 }
663
664 /* Callbacks */
665 static int RequestPage( vlc_object_t *p_this, char const *psz_cmd,
666                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
667 {
668     decoder_sys_t *p_sys = p_data;
669     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
670
671     vlc_mutex_lock( &p_sys->lock );
672     switch( newval.i_int )
673     {
674         case ZVBI_KEY_RED:
675             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[0].pgno );
676             p_sys->i_wanted_subpage = p_sys->nav_link[0].subno;
677             break;
678         case ZVBI_KEY_GREEN:
679             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[1].pgno );
680             p_sys->i_wanted_subpage = p_sys->nav_link[1].subno;
681             break;
682         case ZVBI_KEY_YELLOW:
683             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[2].pgno );
684             p_sys->i_wanted_subpage = p_sys->nav_link[2].subno;
685             break;
686         case ZVBI_KEY_BLUE:
687             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[3].pgno );
688             p_sys->i_wanted_subpage = p_sys->nav_link[3].subno;
689             break;
690         case ZVBI_KEY_INDEX:
691             p_sys->i_wanted_page = vbi_bcd2dec( p_sys->nav_link[5].pgno ); /* #4 is SKIPPED */
692             p_sys->i_wanted_subpage = p_sys->nav_link[5].subno;
693             break;
694     }
695     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 }