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