/src/vlc/modules/codec/telx.c
Line | Count | Source |
1 | | /***************************************************************************** |
2 | | * telx.c : Minimalistic Teletext subtitles decoder |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 2007 Vincent Penne |
5 | | * Some code converted from ProjectX java dvb decoder (c) 2001-2005 by dvb.matt |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify it |
8 | | * under the terms of the GNU Lesser General Public License as published by |
9 | | * the Free Software Foundation; either version 2.1 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * This program is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with this program; if not, write to the Free Software Foundation, |
19 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
20 | | *****************************************************************************/ |
21 | | /***************************************************************************** |
22 | | * |
23 | | * information on teletext format can be found here : |
24 | | * http://pdc.ro.nu/teletext.html |
25 | | * |
26 | | *****************************************************************************/ |
27 | | #ifdef HAVE_CONFIG_H |
28 | | # include "config.h" |
29 | | #endif |
30 | | #include <assert.h> |
31 | | #include <stdint.h> |
32 | | |
33 | | #include <vlc_common.h> |
34 | | #include <vlc_plugin.h> |
35 | | |
36 | | #include <vlc_bits.h> |
37 | | #include <vlc_codec.h> |
38 | | |
39 | | /* #define TELX_DEBUG */ |
40 | | #ifdef TELX_DEBUG |
41 | | # define dbg( a ) msg_Dbg a |
42 | | #else |
43 | 41 | # define dbg( a ) (void) 0 |
44 | | #endif |
45 | | |
46 | | /***************************************************************************** |
47 | | * Module descriptor. |
48 | | *****************************************************************************/ |
49 | | static int Open ( vlc_object_t * ); |
50 | | static int Decode( decoder_t *, block_t * ); |
51 | | |
52 | | #define OVERRIDE_PAGE_TEXT N_("Override page") |
53 | | #define OVERRIDE_PAGE_LONGTEXT N_("Override the indicated page, try this if " \ |
54 | | "your subtitles don't appear (-1 = autodetect from TS, " \ |
55 | | "0 = autodetect from teletext, " \ |
56 | | ">0 = actual page number, usually 888 or 889).") |
57 | | |
58 | | #define IGNORE_SUB_FLAG_TEXT N_("Ignore subtitle flag") |
59 | | #define IGNORE_SUB_FLAG_LONGTEXT N_("Ignore the subtitle flag, try this if " \ |
60 | | "your subtitles don't appear.") |
61 | | |
62 | | #define FRENCH_WORKAROUND_TEXT N_("Workaround for France") |
63 | | #define FRENCH_WORKAROUND_LONGTEXT N_("Some French channels do not flag " \ |
64 | | "their subtitling pages correctly due to a historical " \ |
65 | | "interpretation mistake. Try using this wrong interpretation if " \ |
66 | | "your subtitles don't appear.") |
67 | | |
68 | 172 | vlc_module_begin () |
69 | 86 | set_description( N_("Teletext subtitles decoder") ) |
70 | 86 | set_shortname( "Teletext" ) |
71 | 86 | set_capability( "spu decoder", 50 ) |
72 | 86 | set_subcategory( SUBCAT_INPUT_SCODEC ) |
73 | 86 | set_callback( Open ) |
74 | | |
75 | 86 | add_integer( "telx-override-page", -1, |
76 | 86 | OVERRIDE_PAGE_TEXT, OVERRIDE_PAGE_LONGTEXT ) |
77 | 86 | add_bool( "telx-ignore-subtitle-flag", false, |
78 | 86 | IGNORE_SUB_FLAG_TEXT, IGNORE_SUB_FLAG_LONGTEXT ) |
79 | 86 | add_bool( "telx-french-workaround", false, |
80 | 86 | FRENCH_WORKAROUND_TEXT, FRENCH_WORKAROUND_LONGTEXT ) |
81 | | |
82 | 86 | vlc_module_end () |
83 | | |
84 | | /**************************************************************************** |
85 | | * Local structures |
86 | | ****************************************************************************/ |
87 | | |
88 | | typedef struct |
89 | | { |
90 | | int i_align; |
91 | | bool b_is_subtitle[9]; |
92 | | char ppsz_lines[32][128]; |
93 | | char psz_prev_text[512]; |
94 | | vlc_tick_t prev_pts; |
95 | | int i_page[9]; |
96 | | bool b_erase[9]; |
97 | | const uint16_t * pi_active_national_set[9]; |
98 | | int i_wanted_page, i_wanted_magazine; |
99 | | bool b_ignore_sub_flag; |
100 | | } decoder_sys_t; |
101 | | |
102 | | /**************************************************************************** |
103 | | * Local data |
104 | | ****************************************************************************/ |
105 | | |
106 | | /* |
107 | | * My doc only mentions 13 national characters, but experiments show there |
108 | | * are more, in france for example I already found two more (0x9 and 0xb). |
109 | | * |
110 | | * Conversion is in this order : |
111 | | * |
112 | | * 0x23 0x24 0x40 0x5b 0x5c 0x5d 0x5e 0x5f 0x60 0x7b 0x7c 0x7d 0x7e |
113 | | * (these are the standard ones) |
114 | | * 0x08 0x09 0x0a 0x0b 0x0c 0x0d (apparently a control character) 0x0e 0x0f |
115 | | */ |
116 | | |
117 | | static const uint16_t ppi_national_subsets[][20] = |
118 | | { |
119 | | { 0x00a3, 0x0024, 0x0040, 0x00ab, 0x00bd, 0x00bb, 0x005e, 0x0023, |
120 | | 0x002d, 0x00bc, 0x00a6, 0x00be, 0x00f7 }, /* english ,000 */ |
121 | | |
122 | | { 0x00e9, 0x00ef, 0x00e0, 0x00eb, 0x00ea, 0x00f9, 0x00ee, 0x0023, |
123 | | 0x00e8, 0x00e2, 0x00f4, 0x00fb, 0x00e7, 0, 0x00eb, 0, 0x00ef }, /* french ,001 */ |
124 | | |
125 | | { 0x0023, 0x00a4, 0x00c9, 0x00c4, 0x00d6, 0x00c5, 0x00dc, 0x005f, |
126 | | 0x00e9, 0x00e4, 0x00f6, 0x00e5, 0x00fc }, /* swedish,finnish,hungarian ,010 */ |
127 | | |
128 | | { 0x0023, 0x016f, 0x010d, 0x0165, 0x017e, 0x00fd, 0x00ed, 0x0159, |
129 | | 0x00e9, 0x00e1, 0x011b, 0x00fa, 0x0161 }, /* czech,slovak ,011 */ |
130 | | |
131 | | { 0x0023, 0x0024, 0x00a7, 0x00c4, 0x00d6, 0x00dc, 0x005e, 0x005f, |
132 | | 0x00b0, 0x00e4, 0x00f6, 0x00fc, 0x00df }, /* german ,100 */ |
133 | | |
134 | | { 0x00e7, 0x0024, 0x00a1, 0x00e1, 0x00e9, 0x00ed, 0x00f3, 0x00fa, |
135 | | 0x00bf, 0x00fc, 0x00f1, 0x00e8, 0x00e0 }, /* portuguese,spanish ,101 */ |
136 | | |
137 | | { 0x00a3, 0x0024, 0x00e9, 0x00b0, 0x00e7, 0x00bb, 0x005e, 0x0023, |
138 | | 0x00f9, 0x00e0, 0x00f2, 0x00e8, 0x00ec }, /* italian ,110 */ |
139 | | |
140 | | { 0x0023, 0x00a4, 0x0162, 0x00c2, 0x015e, 0x0102, 0x00ce, 0x0131, |
141 | | 0x0163, 0x00e2, 0x015f, 0x0103, 0x00ee }, /* rumanian ,111 */ |
142 | | |
143 | | /* I have these tables too, but I don't know how they can be triggered */ |
144 | | { 0x0023, 0x0024, 0x0160, 0x0117, 0x0119, 0x017d, 0x010d, 0x016b, |
145 | | 0x0161, 0x0105, 0x0173, 0x017e, 0x012f }, /* lettish,lithuanian ,1000 */ |
146 | | |
147 | | { 0x0023, 0x0144, 0x0105, 0x005a, 0x015a, 0x0141, 0x0107, 0x00f3, |
148 | | 0x0119, 0x017c, 0x015b, 0x0142, 0x017a }, /* polish, 1001 */ |
149 | | |
150 | | { 0x0023, 0x00cb, 0x010c, 0x0106, 0x017d, 0x0110, 0x0160, 0x00eb, |
151 | | 0x010d, 0x0107, 0x017e, 0x0111, 0x0161 }, /* serbian,croatian,slovenian, 1010 */ |
152 | | |
153 | | { 0x0023, 0x00f5, 0x0160, 0x00c4, 0x00d6, 0x017e, 0x00dc, 0x00d5, |
154 | | 0x0161, 0x00e4, 0x00f6, 0x017e, 0x00fc }, /* estonian ,1011 */ |
155 | | |
156 | | { 0x0054, 0x011f, 0x0130, 0x015e, 0x00d6, 0x00c7, 0x00dc, 0x011e, |
157 | | 0x0131, 0x015f, 0x00f6, 0x00e7, 0x00fc }, /* turkish ,1100 */ |
158 | | }; |
159 | | |
160 | | enum |
161 | | { |
162 | | FLAG_ERASE_PAGE = 0x000080, |
163 | | FLAG_NEWSFLASH = 0x004000, |
164 | | FLAG_SUBTITLE = 0x008000, |
165 | | FLAG_SUPPRESS_HEADER = 0x010000, |
166 | | FLAG_UPDATE = 0x020000, |
167 | | FLAG_INTERRUPTED = 0x040000, |
168 | | FLAG_INHIBIT_DISPLAY = 0x080000, |
169 | | FLAG_MAGAZINE_SERIAL = 0x100000, |
170 | | FLAG_FRAGMENT = 0x200000, |
171 | | FLAG_PARTIAL_PAGE = 0x400000, |
172 | | }; |
173 | | |
174 | | /***************************************************************************** |
175 | | * Open: probe the decoder and return score |
176 | | ***************************************************************************** |
177 | | * Tries to launch a decoder and return score so that the interface is able |
178 | | * to chose. |
179 | | *****************************************************************************/ |
180 | | static int Open( vlc_object_t *p_this ) |
181 | 9.76k | { |
182 | 9.76k | decoder_t *p_dec = (decoder_t *) p_this; |
183 | 9.76k | decoder_sys_t *p_sys = NULL; |
184 | 9.76k | int i_val; |
185 | | |
186 | 9.76k | if( p_dec->fmt_in->i_codec != VLC_CODEC_TELETEXT) |
187 | 9.62k | { |
188 | 9.62k | return VLC_EGENERIC; |
189 | 9.62k | } |
190 | | |
191 | 143 | p_dec->pf_decode = Decode; |
192 | 143 | p_sys = p_dec->p_sys = vlc_obj_calloc( p_this, 1, sizeof(*p_sys) ); |
193 | 143 | if( p_sys == NULL ) |
194 | 0 | return VLC_ENOMEM; |
195 | 143 | p_dec->fmt_out.i_codec = 0; |
196 | | |
197 | 143 | p_sys->i_align = 0; |
198 | 1.43k | for ( int i = 0; i < 9; i++ ) |
199 | 1.28k | p_sys->pi_active_national_set[i] = ppi_national_subsets[1]; |
200 | | |
201 | 143 | i_val = var_CreateGetInteger( p_dec, "telx-override-page" ); |
202 | 143 | if( i_val == -1 && p_dec->fmt_in->subs.teletext.i_magazine < 9 && |
203 | 143 | ( p_dec->fmt_in->subs.teletext.i_magazine != 1 || |
204 | 1 | p_dec->fmt_in->subs.teletext.i_page != 0 ) ) /* ignore if TS demux wants page 100 (unlikely to be sub) */ |
205 | 142 | { |
206 | 142 | bool b_val; |
207 | 142 | p_sys->i_wanted_magazine = p_dec->fmt_in->subs.teletext.i_magazine; |
208 | 142 | p_sys->i_wanted_page = p_dec->fmt_in->subs.teletext.i_page; |
209 | | |
210 | 142 | b_val = var_CreateGetBool( p_dec, "telx-french-workaround" ); |
211 | 142 | if( p_sys->i_wanted_page < 100 && |
212 | 142 | (b_val || (p_sys->i_wanted_page % 16) >= 10)) |
213 | 0 | { |
214 | | /* See http://www.nada.kth.se/~ragge/vdr/ttxtsubs/TROUBLESHOOTING.txt |
215 | | * paragraph about French channels - they mix up decimal and |
216 | | * hexadecimal */ |
217 | 0 | p_sys->i_wanted_page = (p_sys->i_wanted_page / 10) * 16 + |
218 | 0 | (p_sys->i_wanted_page % 10); |
219 | 0 | } |
220 | 142 | } |
221 | 1 | else if( i_val <= 0 ) |
222 | 1 | { |
223 | 1 | p_sys->i_wanted_magazine = -1; |
224 | 1 | p_sys->i_wanted_page = -1; |
225 | 1 | } |
226 | 0 | else |
227 | 0 | { |
228 | 0 | p_sys->i_wanted_magazine = i_val / 100; |
229 | 0 | p_sys->i_wanted_page = (((i_val % 100) / 10) << 4) |
230 | 0 | |((i_val % 100) % 10); |
231 | 0 | } |
232 | 143 | p_sys->b_ignore_sub_flag = var_CreateGetBool( p_dec, |
233 | 143 | "telx-ignore-subtitle-flag" ); |
234 | | |
235 | 143 | msg_Dbg( p_dec, "starting telx on magazine %d page %02x flag %d", |
236 | 143 | p_sys->i_wanted_magazine, p_sys->i_wanted_page, |
237 | 143 | p_sys->b_ignore_sub_flag ); |
238 | | |
239 | 143 | return VLC_SUCCESS; |
240 | | |
241 | | /* error: */ |
242 | | /* if (p_sys) { */ |
243 | | /* free(p_sys); */ |
244 | | /* p_sys = NULL; */ |
245 | | /* } */ |
246 | | /* return VLC_EGENERIC; */ |
247 | 143 | } |
248 | | |
249 | | /************************** |
250 | | * change bits endianness * |
251 | | **************************/ |
252 | | static uint8_t bytereverse( int n ) |
253 | 0 | { |
254 | 0 | n = (((n >> 1) & 0x55) | ((n << 1) & 0xaa)); |
255 | 0 | n = (((n >> 2) & 0x33) | ((n << 2) & 0xcc)); |
256 | 0 | n = (((n >> 4) & 0x0f) | ((n << 4) & 0xf0)); |
257 | 0 | return n; |
258 | 0 | } |
259 | | |
260 | | static int hamming_8_4( int a ) |
261 | 82 | { |
262 | 82 | switch (a) { |
263 | 0 | case 0xA8: |
264 | 0 | return 0; |
265 | 0 | case 0x0B: |
266 | 0 | return 1; |
267 | 0 | case 0x26: |
268 | 0 | return 2; |
269 | 0 | case 0x85: |
270 | 0 | return 3; |
271 | 0 | case 0x92: |
272 | 0 | return 4; |
273 | 0 | case 0x31: |
274 | 0 | return 5; |
275 | 0 | case 0x1C: |
276 | 0 | return 6; |
277 | 0 | case 0xBF: |
278 | 0 | return 7; |
279 | 0 | case 0x40: |
280 | 0 | return 8; |
281 | 0 | case 0xE3: |
282 | 0 | return 9; |
283 | 0 | case 0xCE: |
284 | 0 | return 10; |
285 | 0 | case 0x6D: |
286 | 0 | return 11; |
287 | 0 | case 0x7A: |
288 | 0 | return 12; |
289 | 0 | case 0xD9: |
290 | 0 | return 13; |
291 | 0 | case 0xF4: |
292 | 0 | return 14; |
293 | 0 | case 0x57: |
294 | 0 | return 15; |
295 | 82 | default: |
296 | 82 | return -1; // decoding error , not yet corrected |
297 | 82 | } |
298 | 82 | } |
299 | | |
300 | | // utc-2 --> utf-8 |
301 | | // this is not a general function, but it's enough for what we do here |
302 | | // the result buffer need to be at least 4 bytes long |
303 | | static void to_utf8( char * res, uint16_t ch ) |
304 | 0 | { |
305 | 0 | if( ch >= 0x80 ) |
306 | 0 | { |
307 | 0 | if( ch >= 0x800 ) |
308 | 0 | { |
309 | 0 | res[0] = (ch >> 12) | 0xE0; |
310 | 0 | res[1] = ((ch >> 6) & 0x3F) | 0x80; |
311 | 0 | res[2] = (ch & 0x3F) | 0x80; |
312 | 0 | res[3] = 0; |
313 | 0 | } |
314 | 0 | else |
315 | 0 | { |
316 | 0 | res[0] = (ch >> 6) | 0xC0; |
317 | 0 | res[1] = (ch & 0x3F) | 0x80; |
318 | 0 | res[2] = 0; |
319 | 0 | } |
320 | 0 | } |
321 | 0 | else |
322 | 0 | { |
323 | 0 | res[0] = ch; |
324 | 0 | res[1] = 0; |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | | /** |
329 | | * Decode a packet, potentially decoding strings from utc-2 to utf-8. |
330 | | * |
331 | | * Decoding a packet of size \p len will write at most `len * 3 + 1`. |
332 | | */ |
333 | | static void decode_string( char * res, int res_len, |
334 | | decoder_sys_t *p_sys, int magazine, |
335 | | const uint8_t * packet, int len ) |
336 | 0 | { |
337 | 0 | char utf8[7]; |
338 | 0 | char * pt = res; |
339 | |
|
340 | 0 | for ( int i = 0; i < len; i++ ) |
341 | 0 | { |
342 | 0 | int in = bytereverse( packet[i] ) & 0x7f; |
343 | 0 | uint16_t out = 32; |
344 | 0 | size_t l; |
345 | |
|
346 | 0 | switch ( in ) |
347 | 0 | { |
348 | | /* special national characters */ |
349 | 0 | case 0x23: |
350 | 0 | out = p_sys->pi_active_national_set[magazine][0]; |
351 | 0 | break; |
352 | 0 | case 0x24: |
353 | 0 | out = p_sys->pi_active_national_set[magazine][1]; |
354 | 0 | break; |
355 | 0 | case 0x40: |
356 | 0 | out = p_sys->pi_active_national_set[magazine][2]; |
357 | 0 | break; |
358 | 0 | case 0x5b: |
359 | 0 | out = p_sys->pi_active_national_set[magazine][3]; |
360 | 0 | break; |
361 | 0 | case 0x5c: |
362 | 0 | out = p_sys->pi_active_national_set[magazine][4]; |
363 | 0 | break; |
364 | 0 | case 0x5d: |
365 | 0 | out = p_sys->pi_active_national_set[magazine][5]; |
366 | 0 | break; |
367 | 0 | case 0x5e: |
368 | 0 | out = p_sys->pi_active_national_set[magazine][6]; |
369 | 0 | break; |
370 | 0 | case 0x5f: |
371 | 0 | out = p_sys->pi_active_national_set[magazine][7]; |
372 | 0 | break; |
373 | 0 | case 0x60: |
374 | 0 | out = p_sys->pi_active_national_set[magazine][8]; |
375 | 0 | break; |
376 | 0 | case 0x7b: |
377 | 0 | out = p_sys->pi_active_national_set[magazine][9]; |
378 | 0 | break; |
379 | 0 | case 0x7c: |
380 | 0 | out = p_sys->pi_active_national_set[magazine][10]; |
381 | 0 | break; |
382 | 0 | case 0x7d: |
383 | 0 | out = p_sys->pi_active_national_set[magazine][11]; |
384 | 0 | break; |
385 | 0 | case 0x7e: |
386 | 0 | out = p_sys->pi_active_national_set[magazine][12]; |
387 | 0 | break; |
388 | | |
389 | | /* some special control characters (empirical) */ |
390 | 0 | case 0x0d: |
391 | | /* apparently this starts a sequence that ends with 0xb 0xb */ |
392 | 0 | while ( i + 1 < len && (bytereverse( packet[i+1] ) & 0x7f) != 0x0b ) |
393 | 0 | i++; |
394 | 0 | i += 2; |
395 | 0 | break; |
396 | | /* goto skip; */ |
397 | | |
398 | 0 | default: |
399 | | /* non documented national range 0x08 - 0x0f */ |
400 | 0 | if ( in >= 0x08 && in <= 0x0f ) |
401 | 0 | { |
402 | 0 | out = p_sys->pi_active_national_set[magazine][13 + in - 8]; |
403 | 0 | break; |
404 | 0 | } |
405 | | |
406 | | /* normal ascii */ |
407 | 0 | if ( in > 32 && in < 0x7f ) |
408 | 0 | out = in; |
409 | 0 | } |
410 | | |
411 | | /* handle undefined national characters */ |
412 | 0 | if ( out == 0 ) |
413 | 0 | out = 32; |
414 | | |
415 | | /* convert to utf-8 */ |
416 | 0 | to_utf8( utf8, out ); |
417 | 0 | l = strlen( utf8 ); |
418 | 0 | assert(l < 4); |
419 | 0 | if ( pt + l < res + res_len - 1 ) |
420 | 0 | { |
421 | 0 | strcpy(pt, utf8); |
422 | 0 | pt += l; |
423 | 0 | } |
424 | | |
425 | | /* skip: ; */ |
426 | 0 | } |
427 | | /* end: */ |
428 | 0 | *pt++ = 0; |
429 | 0 | } |
430 | | |
431 | | static bool DecodePageHeaderPacket( decoder_t *p_dec, const uint8_t *packet, |
432 | | int magazine ) |
433 | 0 | { |
434 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
435 | |
|
436 | 0 | int flag = 0; |
437 | |
|
438 | 0 | for ( int a = 0; a < 6; a++ ) |
439 | 0 | { |
440 | 0 | flag |= (0xF & (bytereverse( hamming_8_4(packet[8 + a]) ) >> 4)) |
441 | 0 | << (a * 4); |
442 | 0 | } |
443 | | |
444 | | /* if (!p_sys->b_ignore_sub_flag && !(flag & FLAG_SUBTITLE)) */ |
445 | | /* return false; */ |
446 | |
|
447 | 0 | p_sys->i_page[magazine] = (0xF0 & bytereverse( hamming_8_4(packet[7]) )) | /* tens */ |
448 | 0 | (0x0F & (bytereverse( hamming_8_4(packet[6]) ) >> 4) ); /* units */ |
449 | |
|
450 | 0 | char psz_line[(40 - 14) * 3 + 1]; |
451 | 0 | decode_string( psz_line, sizeof(psz_line), p_sys, magazine, |
452 | 0 | packet + 14, 40 - 14 ); |
453 | |
|
454 | 0 | dbg((p_dec, "mag %d flags %x page %x character set %d subtitles %d %s", magazine, flag, |
455 | 0 | p_sys->i_page[magazine], |
456 | 0 | 7 & flag>>21, !!(flag & FLAG_SUBTITLE), psz_line)); |
457 | |
|
458 | 0 | p_sys->pi_active_national_set[magazine] = |
459 | 0 | ppi_national_subsets[7 & (flag >> 21)]; |
460 | |
|
461 | 0 | int subtitlesflags = FLAG_SUBTITLE; |
462 | 0 | if( !p_sys->b_ignore_sub_flag && p_sys->i_wanted_magazine != 0x07 ) |
463 | 0 | subtitlesflags |= FLAG_SUPPRESS_HEADER; |
464 | |
|
465 | 0 | p_sys->b_is_subtitle[magazine] = !((flag & subtitlesflags) != subtitlesflags); |
466 | |
|
467 | 0 | dbg(( p_dec, "FLAGS%s%s%s%s%s%s%s mag_ser %d", |
468 | 0 | (flag & FLAG_ERASE_PAGE) ? " erase" : "", |
469 | 0 | (flag & FLAG_NEWSFLASH) ? " news" : "", |
470 | 0 | (flag & FLAG_SUBTITLE) ? " subtitle" : "", |
471 | 0 | (flag & FLAG_SUPPRESS_HEADER)? " suppressed_head" : "", |
472 | 0 | (flag & FLAG_UPDATE) ? " update" : "", |
473 | 0 | (flag & FLAG_INTERRUPTED) ? " interrupt" : "", |
474 | 0 | (flag & FLAG_INHIBIT_DISPLAY)? " inhibit" : "", |
475 | 0 | !!(flag & FLAG_MAGAZINE_SERIAL) )); |
476 | |
|
477 | 0 | if ( (p_sys->i_wanted_page != -1 |
478 | 0 | && p_sys->i_page[magazine] != p_sys->i_wanted_page) |
479 | 0 | || !p_sys->b_is_subtitle[magazine] ) |
480 | 0 | return false; |
481 | | |
482 | 0 | p_sys->b_erase[magazine] = !!(flag & FLAG_ERASE_PAGE); |
483 | | |
484 | | /* kludge here : |
485 | | * we ignore the erase flag if it happens less than 1.5 seconds |
486 | | * before last caption |
487 | | * TODO make this time configurable |
488 | | * UPDATE the kludge seems to be no more necessary |
489 | | * so it's commented out*/ |
490 | 0 | if ( /*p_block->i_pts > p_sys->prev_pts + 1500000 && */ |
491 | 0 | p_sys->b_erase[magazine] ) |
492 | 0 | { |
493 | 0 | dbg((p_dec, "ERASE !")); |
494 | |
|
495 | 0 | p_sys->b_erase[magazine] = 0; |
496 | 0 | for ( int i = 1; i < 32; i++ ) |
497 | 0 | { |
498 | 0 | if ( !p_sys->ppsz_lines[i][0] ) continue; |
499 | | /* b_update = true; */ |
500 | 0 | p_sys->ppsz_lines[i][0] = 0; |
501 | 0 | } |
502 | 0 | } |
503 | | |
504 | | /* replace the row if it's different */ |
505 | 0 | if ( strcmp(psz_line, p_sys->ppsz_lines[0]) ) |
506 | 0 | { |
507 | 0 | strlcpy( p_sys->ppsz_lines[0], psz_line, |
508 | 0 | sizeof(p_sys->ppsz_lines[0]) ); |
509 | 0 | } |
510 | |
|
511 | 0 | return true; |
512 | 0 | } |
513 | | |
514 | | static bool DecodePacketX1_X23( decoder_t *p_dec, const uint8_t *packet, |
515 | | int magazine, int row, vlc_tick_t pts ) |
516 | 0 | { |
517 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
518 | |
|
519 | 0 | bool b_update = false; |
520 | 0 | char * t; |
521 | 0 | int i; |
522 | |
|
523 | 0 | if ( p_sys->i_wanted_page == -1 && p_sys->i_page[magazine] > 0x99) |
524 | 0 | return false; |
525 | | |
526 | 0 | char psz_line[40 * 3 + 1]; |
527 | 0 | decode_string( psz_line, sizeof(psz_line), p_sys, magazine, |
528 | 0 | packet + 6, 40 ); |
529 | 0 | t = psz_line; |
530 | | |
531 | | /* remove starting spaces */ |
532 | 0 | while ( *t == 32 ) t++; |
533 | | |
534 | | /* remove trailing spaces */ |
535 | 0 | for ( i = strlen(t) - 1; i >= 0 && t[i] == 32; i-- ); |
536 | 0 | t[i + 1] = 0; |
537 | | |
538 | | /* replace the row if it's different */ |
539 | 0 | if ( strcmp( t, p_sys->ppsz_lines[row] ) ) |
540 | 0 | { |
541 | 0 | strlcpy( p_sys->ppsz_lines[row], t, |
542 | 0 | sizeof(p_sys->ppsz_lines[row]) ); |
543 | 0 | b_update = true; |
544 | 0 | } |
545 | |
|
546 | 0 | if (t[0]) |
547 | 0 | p_sys->prev_pts = pts; |
548 | |
|
549 | 0 | dbg((p_dec, "%d %d : ", magazine, row)); |
550 | 0 | dbg((p_dec, "%s", t)); |
551 | |
|
552 | | #ifdef TELX_DEBUG |
553 | | { |
554 | | char dbg[256]; |
555 | | dbg[0] = 0; |
556 | | for ( i = 0; i < 40; i++ ) |
557 | | { |
558 | | int in = bytereverse(packet[6 + i]) & 0x7f; |
559 | | sprintf(dbg + strlen(dbg), "%02x ", in); |
560 | | } |
561 | | dbg((p_dec, "%s", dbg)); |
562 | | dbg[0] = 0; |
563 | | for ( i = 0; i < 40; i++ ) |
564 | | { |
565 | | decode_string( psz_line, sizeof(psz_line), p_sys, magazine, |
566 | | packet + 6 + i, 1 ); |
567 | | sprintf( dbg + strlen(dbg), "%s ", psz_line ); |
568 | | } |
569 | | dbg((p_dec, "%s", dbg)); |
570 | | } |
571 | | #endif |
572 | 0 | return b_update; |
573 | 0 | } |
574 | | |
575 | | |
576 | | static bool DecodePacketX25( decoder_t *p_dec, const uint8_t *packet, |
577 | | int magazine ) |
578 | 0 | { |
579 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
580 | | |
581 | | /* row 25 : alternate header line */ |
582 | 0 | char psz_line[40 * 3 + 1]; |
583 | 0 | decode_string( psz_line, sizeof(psz_line), p_sys, magazine, |
584 | 0 | packet + 6, 40 ); |
585 | | |
586 | | /* replace the row if it's different */ |
587 | 0 | if ( strcmp( psz_line, p_sys->ppsz_lines[0] ) ) |
588 | 0 | { |
589 | 0 | strlcpy( p_sys->ppsz_lines[0], psz_line, |
590 | 0 | sizeof(p_sys->ppsz_lines[0]) ); |
591 | | /* return true; */ |
592 | 0 | } |
593 | |
|
594 | 0 | return false; |
595 | 0 | } |
596 | | |
597 | | static bool DecodeNormalPacket( decoder_t *p_dec, const uint8_t *packet, |
598 | | int magazine, int row, vlc_tick_t pts ) |
599 | 0 | { |
600 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
601 | |
|
602 | 0 | if( (p_sys->i_wanted_page != -1 && |
603 | 0 | p_sys->i_page[magazine] != p_sys->i_wanted_page) |
604 | 0 | || !p_sys->b_is_subtitle[magazine] ) |
605 | 0 | return false; |
606 | | |
607 | 0 | if( row < 24 ) /* row 1-23 : normal lines */ |
608 | 0 | return DecodePacketX1_X23( p_dec, packet, magazine, row, pts ); |
609 | 0 | else if( row == 25 ) /* row 25 : alternate header line */ |
610 | 0 | return DecodePacketX25( p_dec, packet, magazine ); |
611 | 0 | else |
612 | 0 | return false; |
613 | 0 | } |
614 | | |
615 | | |
616 | | /***************************************************************************** |
617 | | * Decode: |
618 | | *****************************************************************************/ |
619 | | static int Decode( decoder_t *p_dec, block_t *p_block ) |
620 | 2.87k | { |
621 | 2.87k | decoder_sys_t *p_sys = p_dec->p_sys; |
622 | 2.87k | subpicture_t *p_spu = NULL; |
623 | | /* int erase = 0; */ |
624 | | #if 0 |
625 | | int i_wanted_magazine = i_conf_wanted_page / 100; |
626 | | int i_wanted_page = 0x10 * ((i_conf_wanted_page % 100) / 10) |
627 | | | (i_conf_wanted_page % 10); |
628 | | #endif |
629 | 2.87k | bool b_update = false; |
630 | 2.87k | char psz_text[512], *pt = psz_text; |
631 | 2.87k | int total; |
632 | | |
633 | 2.87k | if( p_block == NULL ) /* No Drain */ |
634 | 1.68k | return VLCDEC_SUCCESS; |
635 | | |
636 | | // dbg((p_dec, "start of telx packet with header %2x", |
637 | | // * (uint8_t *) p_block->p_buffer)); |
638 | 1.26k | for ( size_t offset = 1; offset + 46 <= p_block->i_buffer; offset += 46 ) |
639 | 83 | { |
640 | 83 | const uint8_t *packet = &p_block->p_buffer[offset]; |
641 | | // int vbi = ((0x20 & packet[2]) != 0 ? 0 : 313) + (0x1F & packet[2]); |
642 | | |
643 | | // dbg((p_dec, "vbi %d header %02x %02x %02x", vbi, packet[0], packet[1], packet[2])); |
644 | 83 | if ( packet[0] == 0xFF ) continue; |
645 | | |
646 | | /* if (packet[1] != 0x2C) { */ |
647 | | /* printf("wrong header\n"); */ |
648 | | /* //goto error; */ |
649 | | /* continue; */ |
650 | | /* } */ |
651 | | |
652 | | /* See EN.300.706 7.1.4 */ |
653 | 41 | int mpag = (hamming_8_4( packet[4] ) << 4) | hamming_8_4( packet[5] ); |
654 | 41 | int row, magazine; |
655 | 41 | if ( mpag < 0 ) |
656 | 41 | { |
657 | | /* decode error */ |
658 | 41 | dbg((p_dec, "mpag hamming error")); |
659 | 41 | continue; |
660 | 41 | } |
661 | | |
662 | | /* magazine number 0-7, row 0-31 (== packet number... or Y) */ |
663 | 0 | row = 0xFF & bytereverse(mpag); |
664 | 0 | magazine = (7 & row) == 0 ? 8 : (7 & row); |
665 | 0 | row >>= 3; |
666 | |
|
667 | 0 | if ( p_sys->i_wanted_page != -1 |
668 | 0 | && magazine != p_sys->i_wanted_magazine ) |
669 | 0 | continue; |
670 | | |
671 | 0 | if ( row == 0 ) /* Page Header Packet */ |
672 | 0 | { |
673 | | /* row 0 : flags and header line */ |
674 | 0 | b_update |= DecodePageHeaderPacket( p_dec, packet, magazine ); |
675 | 0 | if( b_update ) |
676 | 0 | dbg((p_dec, "%ld --> %ld", (long int) p_block->i_pts, |
677 | 0 | (long int)(p_sys->prev_pts+1500000))); |
678 | 0 | } |
679 | 0 | else if ( row < 26 ) /* Normal Packet */ |
680 | 0 | { |
681 | 0 | b_update |= DecodeNormalPacket( p_dec, packet, magazine, row, |
682 | 0 | p_block->i_pts ); |
683 | 0 | } |
684 | | // else if (row >= 26) { /* Non Displayable Packet */ |
685 | | // // row 26 : TV listings |
686 | | // dbg((p_dec, "%d %d : %s", magazine, row, decode_string(p_sys, magazine, packet+6, 40))); |
687 | | // } |
688 | 0 | } |
689 | | |
690 | 1.18k | if ( !b_update ) |
691 | 1.18k | goto error; |
692 | | |
693 | 0 | total = 0; |
694 | 0 | for ( int i = 1; i < 24; i++ ) |
695 | 0 | { |
696 | 0 | size_t l = strlen( p_sys->ppsz_lines[i] ); |
697 | |
|
698 | 0 | if ( l > sizeof(psz_text) - total - 1 ) |
699 | 0 | l = sizeof(psz_text) - total - 1; |
700 | |
|
701 | 0 | if ( l > 0 ) |
702 | 0 | { |
703 | 0 | memcpy( pt, p_sys->ppsz_lines[i], l ); |
704 | 0 | total += l; |
705 | 0 | pt += l; |
706 | 0 | if ( sizeof(psz_text) - total - 1 > 0 ) |
707 | 0 | { |
708 | 0 | *pt++ = '\n'; |
709 | 0 | total++; |
710 | 0 | } |
711 | 0 | } |
712 | 0 | } |
713 | 0 | *pt = 0; |
714 | |
|
715 | 0 | if ( !strcmp(psz_text, p_sys->psz_prev_text) ) |
716 | 0 | goto error; |
717 | | |
718 | 0 | dbg((p_dec, "UPDATE TELETEXT PICTURE")); |
719 | |
|
720 | 0 | assert( sizeof(p_sys->psz_prev_text) >= sizeof(psz_text) ); |
721 | 0 | strcpy( p_sys->psz_prev_text, psz_text ); |
722 | | |
723 | | /* Create the subpicture unit */ |
724 | 0 | p_spu = decoder_NewSubpicture( p_dec, NULL ); |
725 | 0 | if( !p_spu ) |
726 | 0 | { |
727 | 0 | msg_Warn( p_dec, "can't get spu buffer" ); |
728 | 0 | goto error; |
729 | 0 | } |
730 | | |
731 | | /* Create a new subpicture region */ |
732 | 0 | subpicture_region_t *p_region = subpicture_region_NewText(); |
733 | 0 | if( p_region == NULL ) |
734 | 0 | { |
735 | 0 | msg_Err( p_dec, "cannot allocate SPU region" ); |
736 | 0 | goto error; |
737 | 0 | } |
738 | 0 | vlc_spu_regions_push( &p_spu->regions, p_region ); |
739 | | |
740 | | /* Normal text subs, easy markup */ |
741 | 0 | p_region->b_absolute = false; p_region->b_in_window = false; |
742 | 0 | p_region->i_align = SUBPICTURE_ALIGN_BOTTOM | p_sys->i_align; |
743 | 0 | p_region->i_x = p_sys->i_align ? 20 : 0; |
744 | 0 | p_region->i_y = 10; |
745 | 0 | p_region->p_text = text_segment_New(psz_text); |
746 | |
|
747 | 0 | p_spu->i_start = p_block->i_pts; |
748 | 0 | p_spu->i_stop = p_block->i_pts + p_block->i_length; |
749 | 0 | p_spu->b_ephemer = (p_block->i_length == 0); |
750 | 0 | dbg((p_dec, "%ld --> %ld", (long int) p_block->i_pts/100000, (long int)p_block->i_length/100000)); |
751 | |
|
752 | 0 | block_Release( p_block ); |
753 | 0 | if( p_spu != NULL ) |
754 | 0 | decoder_QueueSub( p_dec, p_spu ); |
755 | 0 | return VLCDEC_SUCCESS; |
756 | | |
757 | 1.18k | error: |
758 | 1.18k | if ( p_spu != NULL ) |
759 | 0 | { |
760 | 0 | subpicture_Delete( p_spu ); |
761 | 0 | p_spu = NULL; |
762 | 0 | } |
763 | | |
764 | 1.18k | block_Release( p_block ); |
765 | 1.18k | return VLCDEC_SUCCESS; |
766 | 0 | } |