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