blob: 23699d9c6b19633e582897c7b81063537c6d2bd2
1 | /* |
2 | * libzvbi -- Closed Caption decoder |
3 | * |
4 | * Copyright (C) 2000, 2001, 2002 Michael H. Schimek |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public |
17 | * License along with this library; if not, write to the |
18 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
19 | * Boston, MA 02110-1301 USA. |
20 | */ |
21 | |
22 | /* $Id: caption.c,v 1.28 2008/02/24 14:18:03 mschimek Exp $ */ |
23 | |
24 | #ifdef HAVE_CONFIG_H |
25 | # include "config.h" |
26 | #endif |
27 | |
28 | #include <unistd.h> |
29 | |
30 | #include "misc.h" |
31 | #include "trigger.h" |
32 | #include "format.h" |
33 | #include "lang.h" |
34 | #include "hamm.h" |
35 | #include "tables.h" |
36 | #include "vbi.h" |
37 | #include <android/log.h> |
38 | #include "am_debug.h" |
39 | |
40 | #define elements(array) (sizeof(array) / sizeof(array[0])) |
41 | |
42 | #define LOG_TAG "ZVBI" |
43 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) |
44 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) |
45 | |
46 | |
47 | #define ITV_DEBUG(x) /* x */ |
48 | #define XDS_SEP_DEBUG /* x */ printf |
49 | #define XDS_SEP_DUMP(x) /* x */ |
50 | #define CC_DUMP(x) /* x */ |
51 | #define CC_TEXT_DUMP(x) /* x */ |
52 | |
53 | static inline void |
54 | caption_send_event(vbi_decoder *vbi, vbi_event *ev) |
55 | { |
56 | /* Permits calling vbi_fetch_cc_page from handler */ |
57 | pthread_mutex_unlock(&vbi->cc.mutex); |
58 | |
59 | vbi_send_event(vbi, ev); |
60 | |
61 | pthread_mutex_lock(&vbi->cc.mutex); |
62 | } |
63 | |
64 | /* |
65 | * XDS (Extended Data Service) decoder |
66 | */ |
67 | |
68 | #define XDS_CURRENT 0 |
69 | #define XDS_FUTURE 1 |
70 | #define XDS_CHANNEL 2 |
71 | #define XDS_MISC 3 |
72 | #define XDS_PUBLIC_SERVICE 4 |
73 | #define XDS_RESERVED 5 |
74 | #define XDS_UNDEFINED 6 /* proprietary format */ |
75 | |
76 | #define XDS_END 15 |
77 | |
78 | /* vbi_classify_page, program_info language */ |
79 | static const char * |
80 | language[8] = { |
81 | "Unknown", |
82 | "English", |
83 | "Español", |
84 | "Français", |
85 | "Deutsch", |
86 | "Italiano", |
87 | "Other", |
88 | "None" |
89 | }; |
90 | |
91 | static uint32_t hcrc[128]; |
92 | |
93 | static void init_hcrc(void) __attribute__ ((constructor)); |
94 | |
95 | |
96 | /* |
97 | http://www.fcc.gov/cgb/statid.html |
98 | * |
99 | * XDS has no unique station id as EBU (or is the call sign?) |
100 | * so we create a checksum over the station name. |
101 | */ |
102 | static void |
103 | init_hcrc(void) |
104 | { |
105 | unsigned int sum; |
106 | int i, j; |
107 | |
108 | for (i = 0; i < 128; i++) { |
109 | sum = 0; |
110 | for (j = 7 - 1; j >= 0; j--) |
111 | if (i & (1 << j)) |
112 | sum ^= 0x48000000L >> j; |
113 | hcrc[i] = sum; |
114 | } |
115 | } |
116 | |
117 | static int |
118 | xds_strfu(signed char *d, const uint8_t *s, int len) |
119 | { |
120 | int c, neq = 0; |
121 | |
122 | for (; len > 0 && *s <= 0x20; s++, len--); |
123 | |
124 | for (; len > 0; s++, len--) { |
125 | c = MAX((uint8_t) 0x20, *s); |
126 | neq |= *d ^ c; |
127 | *d++ = c; |
128 | } |
129 | |
130 | neq |= *d; |
131 | *d = 0; |
132 | |
133 | return neq; |
134 | } |
135 | |
136 | #define xds_intfu(d, val) (neq |= d ^ (val), d = (val)) |
137 | |
138 | static void |
139 | flush_prog_info(vbi_decoder *vbi, vbi_program_info *pi, vbi_event *e) |
140 | { |
141 | e->ev.aspect = pi->aspect; |
142 | |
143 | vbi_reset_prog_info(pi); |
144 | |
145 | if (memcmp(&e->ev.aspect, &pi->aspect, sizeof(pi->aspect)) != 0) { |
146 | e->type = VBI_EVENT_ASPECT; |
147 | caption_send_event(vbi, e); |
148 | } |
149 | |
150 | vbi->cc.info_cycle[pi->future] = 0; |
151 | } |
152 | |
153 | static inline void |
154 | xds_decoder(vbi_decoder *vbi, int _class, int type, |
155 | uint8_t *buffer, int length) |
156 | { |
157 | XDS_SEP_DEBUG("xds_decoder _class = %d,type = %d, buffer[0]=%02x, buffer[1]=%02x length = %d,\n",_class ,type,buffer[0], buffer[1],length); |
158 | vbi_network *n = &vbi->network.ev.network; |
159 | vbi_program_info *pi; |
160 | int neq, i; |
161 | vbi_event e; |
162 | |
163 | assert(length > 0 && length <= 32); |
164 | |
165 | // XXX we have no indication how long the program info applies. |
166 | // It will be canceled on channel switch, but who knows |
167 | // what the station transmits when the next program starts. |
168 | // (Nothing, possibly.) A timeout seems necessary. |
169 | |
170 | switch (_class) { |
171 | case XDS_CURRENT: /* 0 */ |
172 | case XDS_FUTURE: /* 1 */ |
173 | if (!(vbi->event_mask & (VBI_EVENT_ASPECT | VBI_EVENT_PROG_INFO | VBI_EVENT_RATING))){ |
174 | XDS_SEP_DEBUG("vbi->event_mask & VBI_EVENT_ASPECT | VBI_EVENT_PROG_INFO"); |
175 | return; |
176 | } |
177 | pi = &vbi->prog_info[_class]; |
178 | neq = 0; |
179 | |
180 | switch (type) { |
181 | case 1: /* program identification number */ |
182 | { |
183 | int month, day, hour, min; |
184 | |
185 | if (length != 4) |
186 | return; |
187 | |
188 | month = buffer[3] & 15; |
189 | day = buffer[2] & 31; |
190 | hour = buffer[1] & 31; |
191 | min = buffer[0] & 63; |
192 | |
193 | if (month == 0 || month > 12 |
194 | || day == 0 || day > 31 |
195 | || hour > 23 || min > 59) |
196 | return; |
197 | |
198 | month--; |
199 | day--; |
200 | |
201 | neq = (pi->month ^ month) | (pi->day ^ day) |
202 | | (pi->hour ^ hour) | (pi->min ^ min); |
203 | |
204 | pi->tape_delayed = !!(buffer[3] & 0x10); |
205 | |
206 | if (neq) { |
207 | flush_prog_info(vbi, pi, &e); |
208 | |
209 | pi->month = month; |
210 | pi->day = day; |
211 | pi->hour = hour; |
212 | pi->min = min; |
213 | |
214 | pi->tape_delayed = !!(buffer[3] & 0x10); |
215 | } |
216 | |
217 | break; |
218 | } |
219 | |
220 | case 2: /* program length */ |
221 | { |
222 | int lhour, lmin, ehour = -1, emin = -1, esec = 0; |
223 | |
224 | if (length < 2 || length > 6) |
225 | return; |
226 | |
227 | lhour = buffer[1] & 63; |
228 | lmin = buffer[0] & 63; |
229 | |
230 | if (length >= 3) { |
231 | ehour = buffer[3] & 63; |
232 | emin = buffer[2] & 63; |
233 | |
234 | if (length >= 5) |
235 | esec = buffer[4] & 63; |
236 | } |
237 | |
238 | if (lmin > 59 || emin > 59 || esec > 59) |
239 | return; |
240 | |
241 | xds_intfu(pi->length_hour, lhour); |
242 | xds_intfu(pi->length_min, lmin); |
243 | xds_intfu(pi->elapsed_hour, ehour); |
244 | xds_intfu(pi->elapsed_min, emin); |
245 | xds_intfu(pi->elapsed_sec, esec); |
246 | |
247 | break; |
248 | } |
249 | |
250 | case 3: /* program name */ |
251 | if (length < 2) |
252 | return; |
253 | |
254 | neq = xds_strfu(pi->title, buffer, length); |
255 | |
256 | if (!neq) { /* no title change */ |
257 | if (!(vbi->cc.info_cycle[_class] & (1 << 3))) |
258 | break; /* already reported */ |
259 | |
260 | if (!(vbi->cc.info_cycle[_class] & (1 << 1))) { |
261 | /* Second occurence without PIN */ |
262 | |
263 | flush_prog_info(vbi, pi, &e); |
264 | |
265 | xds_strfu(pi->title, buffer, length); |
266 | vbi->cc.info_cycle[_class] |= 1 << 3; |
267 | } |
268 | } |
269 | |
270 | break; |
271 | |
272 | case 4: /* program type */ |
273 | { |
274 | int neq; |
275 | |
276 | neq = (pi->type_classf != VBI_PROG_CLASSF_EIA_608); |
277 | pi->type_classf = VBI_PROG_CLASSF_EIA_608; |
278 | |
279 | for (i = 0; i < length; i++) { |
280 | neq |= pi->type_id[i] ^ buffer[i]; |
281 | pi->type_id[i] = buffer[i]; |
282 | } |
283 | |
284 | neq |= pi->type_id[i]; |
285 | pi->type_id[i] = 0; |
286 | |
287 | break; |
288 | } |
289 | |
290 | case 5: /* program rating */ |
291 | { |
292 | vbi_rating_auth auth; |
293 | int r, g, dlsv = 0; |
294 | |
295 | if (length != 2) |
296 | return; |
297 | |
298 | r = buffer[0] & 7; |
299 | g = buffer[1] & 7; |
300 | XDS_SEP_DEBUG("r = %d,g = %d\n",r,g); |
301 | if (buffer[0] & 0x20) |
302 | dlsv |= VBI_RATING_D; |
303 | if (buffer[1] & 0x08) |
304 | dlsv |= VBI_RATING_L; |
305 | if (buffer[1] & 0x10) |
306 | dlsv |= VBI_RATING_S; |
307 | if (buffer[1] & 0x20) |
308 | dlsv |= VBI_RATING_V; |
309 | |
310 | if ((buffer[0] & 0x08) == 0) { |
311 | //if (r == 0) return; |
312 | auth = VBI_RATING_AUTH_MPAA; |
313 | pi->rating.dlsv = dlsv = 0; |
314 | } else if ((buffer[0] & 0x10) == 0) { |
315 | auth = VBI_RATING_AUTH_TV_US; |
316 | r = g; |
317 | } else if ((buffer[1] & 0x08) == 0) { |
318 | if ((buffer[0] & 0x20) == 0) { |
319 | if ((r = g) > 6) return; |
320 | auth = VBI_RATING_AUTH_TV_CA_EN; |
321 | } else { |
322 | if ((r = g) > 5) return; |
323 | auth = VBI_RATING_AUTH_TV_CA_FR; |
324 | } |
325 | pi->rating.dlsv = dlsv = 0; |
326 | } else |
327 | return; |
328 | |
329 | if ((neq = (pi->rating.auth != auth |
330 | || pi->rating.id != r |
331 | || pi->rating.dlsv != dlsv))) { |
332 | pi->rating.auth = auth; |
333 | pi->rating.id = r; |
334 | pi->rating.dlsv = dlsv; |
335 | } |
336 | if (vbi->event_mask & VBI_EVENT_RATING){ |
337 | e.type = VBI_EVENT_RATING; |
338 | e.ev.prog_info = pi; |
339 | caption_send_event(vbi, &e); |
340 | } |
341 | |
342 | break; |
343 | } |
344 | |
345 | case 6: /* program audio services */ |
346 | { |
347 | static const vbi_audio_mode mode[2][8] = { |
348 | { |
349 | VBI_AUDIO_MODE_UNKNOWN, |
350 | VBI_AUDIO_MODE_MONO, |
351 | VBI_AUDIO_MODE_SIMULATED_STEREO, |
352 | VBI_AUDIO_MODE_STEREO, |
353 | VBI_AUDIO_MODE_STEREO_SURROUND, |
354 | VBI_AUDIO_MODE_DATA_SERVICE, |
355 | VBI_AUDIO_MODE_UNKNOWN, /* "other" */ |
356 | VBI_AUDIO_MODE_NONE |
357 | }, { |
358 | VBI_AUDIO_MODE_UNKNOWN, |
359 | VBI_AUDIO_MODE_MONO, |
360 | VBI_AUDIO_MODE_VIDEO_DESCRIPTIONS, |
361 | VBI_AUDIO_MODE_NON_PROGRAM_AUDIO, |
362 | VBI_AUDIO_MODE_SPECIAL_EFFECTS, |
363 | VBI_AUDIO_MODE_DATA_SERVICE, |
364 | VBI_AUDIO_MODE_UNKNOWN, /* "other" */ |
365 | VBI_AUDIO_MODE_NONE |
366 | } |
367 | }; |
368 | |
369 | if (length != 2) |
370 | return; |
371 | |
372 | for (i = 0; i < 2; i++) { |
373 | int l = (buffer[i] >> 3) & 7; |
374 | vbi_audio_mode m = mode[i][buffer[i] & 7]; |
375 | /* should be const char *, but I got that |
376 | wrong and cannot change the public |
377 | pi->audio[].language type now. */ |
378 | unsigned char *s = ((1 << l) & 0xC1) ? NULL : |
379 | (unsigned char *) language[l]; |
380 | |
381 | if (pi->audio[i].mode != m) { |
382 | neq = 1; pi->audio[i].mode = m; |
383 | } |
384 | if (pi->audio[i].language != s) { |
385 | neq = 1; pi->audio[i].language = s; |
386 | } |
387 | } |
388 | |
389 | break; |
390 | } |
391 | |
392 | case 7: /* program caption services */ |
393 | { |
394 | int services = 0; |
395 | |
396 | if (length > 8) |
397 | return; |
398 | |
399 | for (i = 0; i < 8; i++) |
400 | pi->caption_language[i] = NULL; |
401 | |
402 | for (i = 0; i < length; i++) { |
403 | int ch = buffer[i] & 7; |
404 | int l = (buffer[i] >> 3) & 7; |
405 | /* should be const char *, but I got that |
406 | wrong and cannot change the public |
407 | pi->audio[].language type now. */ |
408 | unsigned char *s; |
409 | |
410 | ch = (ch & 1) * 4 + (ch >> 1); |
411 | |
412 | services |= 1 << ch; |
413 | s = ((1 << l) & 0xC1) ? NULL : |
414 | (unsigned char *) language[l]; |
415 | |
416 | if (pi->caption_language[ch] != (unsigned char *) s) { |
417 | neq = 1; pi->caption_language[ch] = (unsigned char *) s; |
418 | } |
419 | |
420 | if (_class == XDS_CURRENT) |
421 | vbi->cc.channel[ch].language = |
422 | pi->caption_language[ch]; |
423 | } |
424 | |
425 | xds_intfu(pi->caption_services, services); |
426 | |
427 | break; |
428 | } |
429 | |
430 | case 8: /* copy generation management system */ |
431 | if (length != 1) |
432 | return; |
433 | |
434 | xds_intfu(pi->cgms_a, buffer[0] & 63); |
435 | |
436 | break; |
437 | |
438 | case 9: /* program aspect ratio */ |
439 | { |
440 | vbi_aspect_ratio *r = &e.ev.aspect; |
441 | |
442 | if (length > 3) |
443 | return; |
444 | |
445 | memset(&e, 0, sizeof(e)); |
446 | |
447 | r->first_line = (buffer[0] & 63) + 22; |
448 | r->last_line = 262 - (buffer[1] & 63); |
449 | r->film_mode = 0; |
450 | r->open_subtitles = VBI_SUBT_UNKNOWN; |
451 | |
452 | if (length >= 3 && (buffer[2] & 1)) |
453 | r->ratio = 16.0 / 9.0; |
454 | else |
455 | r->ratio = 1.0; |
456 | |
457 | if (memcmp(r, &vbi->prog_info[0].aspect, sizeof(*r)) != 0) { |
458 | vbi->prog_info[0].aspect = *r; |
459 | vbi->aspect_source = 3; |
460 | |
461 | e.type = VBI_EVENT_ASPECT; |
462 | caption_send_event(vbi, &e); |
463 | |
464 | neq = 1; |
465 | } |
466 | |
467 | break; |
468 | } |
469 | |
470 | case 0x10 ... 0x17: /* program description */ |
471 | { |
472 | int line = type & 7; |
473 | |
474 | neq = xds_strfu(pi->description[line], buffer, length); |
475 | |
476 | break; |
477 | } |
478 | |
479 | default: |
480 | return; /* no event */ |
481 | } |
482 | |
483 | if (0) |
484 | printf("[type %d cycle %08x class %d neq %d]\n", |
485 | type, vbi->cc.info_cycle[_class], _class, neq); |
486 | |
487 | if (neq){ /* first occurence of this type with this data */ |
488 | vbi->cc.info_cycle[_class] |= 1 << type; |
489 | } |
490 | else if (vbi->cc.info_cycle[_class] & (1 << type)) { |
491 | /* Second occurance of this type with same data */ |
492 | |
493 | e.type = VBI_EVENT_PROG_INFO; |
494 | e.ev.prog_info = pi; |
495 | |
496 | caption_send_event(vbi, &e); |
497 | XDS_SEP_DEBUG("caption_send_event VBI_EVENT_PROG_INFO\n"); |
498 | vbi->cc.info_cycle[_class] = 0; /* all changes reported */ |
499 | } |
500 | |
501 | break; |
502 | |
503 | case XDS_CHANNEL: |
504 | n->ts_id = -1; |
505 | |
506 | switch (type) { |
507 | case 1: /* network name */ |
508 | if (xds_strfu(n->name, buffer, length)) { |
509 | n->cycle = 1; |
510 | } else if (n->cycle == 1) { |
511 | signed char *s = n->name; |
512 | uint32_t sum; |
513 | |
514 | if (n->call[0]) |
515 | s = n->call; |
516 | |
517 | for (sum = 0; *s; s++) |
518 | sum = (sum >> 7) ^ hcrc[(sum ^ *s) & 0x7F]; |
519 | |
520 | sum &= ((1UL << 31) - 1); |
521 | sum |= 1UL << 30; |
522 | |
523 | if (n->nuid != 0) |
524 | vbi_chsw_reset(vbi, sum); |
525 | |
526 | n->nuid = sum; |
527 | |
528 | vbi->network.type = VBI_EVENT_NETWORK; |
529 | caption_send_event(vbi, &vbi->network); |
530 | |
531 | vbi->network.type = VBI_EVENT_NETWORK_ID; |
532 | caption_send_event(vbi, &vbi->network); |
533 | |
534 | n->cycle = 3; |
535 | } |
536 | |
537 | break; |
538 | |
539 | case 2: /* network call letters */ |
540 | if (xds_strfu(n->call, buffer, length)) { |
541 | if (n->cycle != 1) { |
542 | n->name[0] = 0; |
543 | n->cycle = 0; |
544 | } |
545 | } |
546 | |
547 | break; |
548 | |
549 | case 3: /* channel tape delay */ |
550 | if (length != 2) |
551 | return; |
552 | |
553 | n->tape_delay = |
554 | (buffer[1] & 31) * 60 + (buffer[0] & 63); |
555 | |
556 | break; |
557 | |
558 | case 4: /* Transmission Signal ID */ |
559 | if (length < 4) |
560 | return; |
561 | |
562 | n->ts_id = |
563 | ((buffer[3] & 15) << 0) |
564 | |((buffer[2] & 15) << 4) |
565 | |((buffer[1] & 15) << 8) |
566 | |((buffer[0] & 15) << 12); |
567 | |
568 | vbi->network.type = VBI_EVENT_NETWORK; |
569 | caption_send_event(vbi, &vbi->network); |
570 | break; |
571 | |
572 | default: |
573 | break; |
574 | } |
575 | |
576 | break; |
577 | |
578 | case XDS_MISC: |
579 | switch (type) { |
580 | case 1: /* time of day */ |
581 | if (length != 6) |
582 | return; |
583 | break; |
584 | |
585 | case 2: /* impulse capture id */ |
586 | if (length != 6) |
587 | return; |
588 | break; |
589 | |
590 | case 3: /* supplemental data location */ |
591 | break; |
592 | |
593 | case 4: /* local time zone */ |
594 | if (length != 1) |
595 | return; |
596 | break; |
597 | |
598 | case 0x40: /* out-of-band channel number */ |
599 | if (length != 2) |
600 | return; |
601 | break; |
602 | |
603 | default: |
604 | break; |
605 | } |
606 | |
607 | break; |
608 | |
609 | default: |
610 | break; |
611 | } |
612 | } |
613 | |
614 | static void |
615 | xds_separator(vbi_decoder *vbi, uint8_t *buf) |
616 | { |
617 | struct caption *cc = &vbi->cc; |
618 | xds_sub_packet *sp = cc->curr_sp; |
619 | int c1 = vbi_unpar8 (buf[0]); |
620 | int c2 = vbi_unpar8 (buf[1]); |
621 | unsigned int class, type; |
622 | |
623 | XDS_SEP_DEBUG("XDS %02x %02x\n", buf[0], buf[1]); |
624 | |
625 | if ((c1 | c2) < 0) { |
626 | XDS_SEP_DEBUG("XDS tx error, discard current packet\n"); |
627 | |
628 | if (sp) { |
629 | sp->count = 0; |
630 | sp->chksum = 0; |
631 | sp = NULL; |
632 | } |
633 | |
634 | return; |
635 | } |
636 | |
637 | switch (c1) { |
638 | case 1 ... 14: |
639 | class = (c1 - 1) >> 1; |
640 | |
641 | if (class > elements(cc->sub_packet) |
642 | || c2 > (int) elements(cc->sub_packet[0])) { |
643 | XDS_SEP_DEBUG("XDS ignore packet %d/0x%02x\n", class, c2); |
644 | cc->curr_sp = NULL; |
645 | return; |
646 | } |
647 | |
648 | cc->curr_sp = sp = &cc->sub_packet[class][c2]; |
649 | |
650 | if (c1 & 1) { /* start */ |
651 | sp->chksum = c1 + c2; |
652 | sp->count = 2; |
653 | } else if (!sp->count) { |
654 | XDS_SEP_DEBUG("XDS can't continue %d/0x%02x\n", class, c2); |
655 | cc->curr_sp = NULL; |
656 | } |
657 | |
658 | return; |
659 | |
660 | case 15: |
661 | if (!sp) |
662 | return; |
663 | |
664 | sp->chksum += c1 + c2; |
665 | |
666 | class = (sp - cc->sub_packet[0]) / elements(cc->sub_packet[0]); |
667 | type = (sp - cc->sub_packet[0]) % elements(cc->sub_packet[0]); |
668 | |
669 | if (sp->chksum & 0x7F) { |
670 | XDS_SEP_DEBUG("XDS ignore packet %d/0x%02x, " |
671 | "checksum error\n", class, type); |
672 | } else if (sp->count <= 2) { |
673 | XDS_SEP_DEBUG("XDS ignore empty packet " |
674 | "%d/0x%02x\n", class, type); |
675 | } else { |
676 | xds_decoder(vbi, class, type, sp->buffer, sp->count - 2); |
677 | |
678 | XDS_SEP_DUMP( |
679 | for (i = 0; i < sp->count - 2; i++) |
680 | printf("%c", (sp->buffer[i])); //printf("%c", _vbi_to_ascii (sp->buffer[i])); |
681 | printf(" %d/0x%02x\n", class, type); |
682 | ) |
683 | } |
684 | |
685 | sp->count = 0; |
686 | sp->chksum = 0; |
687 | cc->curr_sp = NULL; |
688 | |
689 | return; |
690 | |
691 | case 0x20 ... 0x7F: |
692 | if (!sp) |
693 | return; |
694 | |
695 | if (sp->count >= 32 + 2) { |
696 | XDS_SEP_DEBUG("XDS packet length overflow, discard %d/0x%02x\n", |
697 | (sp - cc->sub_packet[0]) / elements(cc->sub_packet[0]), |
698 | (sp - cc->sub_packet[0]) % elements(cc->sub_packet[0])); |
699 | |
700 | sp->count = 0; |
701 | sp->chksum = 0; |
702 | cc->curr_sp = NULL; |
703 | return; |
704 | } |
705 | |
706 | sp->buffer[sp->count - 2] = c1; |
707 | sp->buffer[sp->count - 1] = c2; |
708 | sp->chksum += c1 + c2; |
709 | sp->count += 1 + !!c2; |
710 | |
711 | return; |
712 | |
713 | default: |
714 | assert(!"reached"); |
715 | } |
716 | } |
717 | |
718 | static void |
719 | itv_separator(vbi_decoder *vbi, struct caption *cc, char c) |
720 | { |
721 | if (ITV_DEBUG(0 &&) !(vbi->event_mask & VBI_EVENT_TRIGGER)) |
722 | return; |
723 | |
724 | if (c >= 0x20) { |
725 | if (c == '<') // s4-nbc omitted CR |
726 | itv_separator(vbi, cc, 0); |
727 | else if (cc->itv_count > (int) sizeof(cc->itv_buf) - 2) |
728 | cc->itv_count = 0; |
729 | |
730 | cc->itv_buf[cc->itv_count++] = c; |
731 | |
732 | return; |
733 | } |
734 | |
735 | cc->itv_buf[cc->itv_count] = 0; |
736 | cc->itv_count = 0; |
737 | |
738 | ITV_DEBUG(printf("ITV: <%s>\n", cc->itv_buf)); |
739 | |
740 | vbi_atvef_trigger(vbi, cc->itv_buf); |
741 | } |
742 | |
743 | /* |
744 | * Closed Caption decoder |
745 | */ |
746 | |
747 | #define ROWS 15 |
748 | #define COLUMNS 34 |
749 | |
750 | static void |
751 | render(vbi_page *pg, int row) |
752 | { |
753 | vbi_event event; |
754 | |
755 | if (row < 0 || pg->dirty.roll) { |
756 | /* no particular row or not fetched |
757 | since last roll/clear, redraw all */ |
758 | pg->dirty.y0 = 0; |
759 | pg->dirty.y1 = ROWS - 1; |
760 | pg->dirty.roll = 0; |
761 | } else { |
762 | pg->dirty.y0 = MIN(row, pg->dirty.y0); |
763 | pg->dirty.y1 = MAX(row, pg->dirty.y1); |
764 | } |
765 | |
766 | event.type = VBI_EVENT_CAPTION; |
767 | event.ev.caption.pgno = pg->pgno; |
768 | |
769 | caption_send_event(pg->vbi, &event); |
770 | } |
771 | |
772 | static void |
773 | clear(vbi_page *pg) |
774 | { |
775 | vbi_event event; |
776 | |
777 | pg->dirty.y0 = 0; |
778 | pg->dirty.y1 = ROWS - 1; |
779 | pg->dirty.roll = -ROWS; |
780 | |
781 | event.type = VBI_EVENT_CAPTION; |
782 | event.ev.caption.pgno = pg->pgno; |
783 | |
784 | caption_send_event(pg->vbi, &event); |
785 | } |
786 | |
787 | static void |
788 | roll_up(vbi_page *pg, int first_row, int last_row) |
789 | { |
790 | vbi_event event; |
791 | |
792 | if (pg->dirty.roll != 0 || pg->dirty.y0 <= pg->dirty.y1) { |
793 | /* not fetched since last update, redraw all */ |
794 | pg->dirty.roll = 0; |
795 | pg->dirty.y0 = MIN(first_row, pg->dirty.y0); |
796 | pg->dirty.y1 = MAX(last_row, pg->dirty.y1); |
797 | } else { |
798 | pg->dirty.roll = -1; |
799 | pg->dirty.y0 = first_row; |
800 | pg->dirty.y1 = last_row; |
801 | } |
802 | |
803 | event.type = VBI_EVENT_CAPTION; |
804 | event.ev.caption.pgno = pg->pgno; |
805 | |
806 | caption_send_event(pg->vbi, &event); |
807 | } |
808 | |
809 | static inline void |
810 | update(cc_channel *ch) |
811 | { |
812 | #if 1 |
813 | int i; |
814 | vbi_char *acp = ch->line - ch->pg[ch->hidden].text + ch->pg[ch->hidden^1].text; |
815 | |
816 | memcpy(acp, ch->line, sizeof(*acp) * COLUMNS); |
817 | render(&ch->pg[ch->hidden^1], ch->row); |
818 | #if 0 |
819 | AM_DEBUG(1,"+++ Display line"); |
820 | for (i=0;i<sizeof(*acp) * COLUMNS;i++) |
821 | { |
822 | AM_DEBUG(1,"+++ %x ", ch->line[i].unicode); |
823 | } |
824 | #endif |
825 | #endif |
826 | } |
827 | |
828 | static void |
829 | word_break(struct caption *cc, cc_channel *ch, int upd) |
830 | { |
831 | cc = cc; |
832 | |
833 | /* |
834 | * Add a leading and trailing space. |
835 | */ |
836 | #if 0 |
837 | if (ch->col > ch->col1) { |
838 | vbi_char c = ch->line[ch->col1]; |
839 | |
840 | if ((c.unicode & 0x7F) != 0x20 |
841 | && ch->line[ch->col1 - 1].opacity == VBI_TRANSPARENT_SPACE) { |
842 | c.unicode = 0x20; |
843 | ch->line[ch->col1 - 1] = c; |
844 | } |
845 | |
846 | c = ch->line[ch->col - 1]; |
847 | |
848 | if ((c.unicode & 0x7F) != 0x20 |
849 | && ch->line[ch->col].opacity == VBI_TRANSPARENT_SPACE) { |
850 | c.unicode = 0x20; |
851 | ch->line[ch->col] = c; |
852 | } |
853 | } |
854 | #endif |
855 | if (!upd || ch->mode == MODE_POP_ON) |
856 | return; |
857 | |
858 | /* |
859 | * NB we render only at spaces (end of word) and |
860 | * before cursor motions and mode switching, to keep the |
861 | * drawing efforts (scaling etc) at a minimum. update() |
862 | * for double buffering at word granularity. |
863 | * |
864 | * XXX should not render if space follows space, |
865 | * but force in long words. |
866 | */ |
867 | ch->update = 1; |
868 | } |
869 | |
870 | static inline void |
871 | set_cursor(cc_channel *ch, int col, int row) |
872 | { |
873 | ch->col = ch->col1 = col; |
874 | ch->row = row; |
875 | |
876 | ch->line = ch->pg[ch->hidden].text + row * COLUMNS; |
877 | } |
878 | |
879 | static void |
880 | put_char(struct caption *cc, cc_channel *ch, vbi_char c) |
881 | { |
882 | /* c.foreground = rand() & 7; */ |
883 | /* c.background = rand() & 7; */ |
884 | |
885 | if (ch->col < COLUMNS - 1) |
886 | ch->line[ch->col++] = c; |
887 | else { |
888 | /* line break here? */ |
889 | |
890 | ch->line[COLUMNS - 2] = c; |
891 | } |
892 | if (ch->mode == MODE_POP_ON) |
893 | return; |
894 | ch->update = 1; |
895 | //if ((c.unicode & 0x7F) == 0x20) |
896 | // word_break(cc, ch, 1); |
897 | //AM_DEBUG(1, "add render in putchar"); |
898 | } |
899 | |
900 | static inline cc_channel * |
901 | switch_channel(struct caption *cc, cc_channel *ch, int new_chan) |
902 | { |
903 | word_break(cc, ch, 1); // we leave for a number of frames |
904 | |
905 | return &cc->channel[cc->curr_chan = new_chan]; |
906 | } |
907 | |
908 | static void |
909 | erase_memory(struct caption *cc, cc_channel *ch, int page) |
910 | { |
911 | vbi_page *pg = ch->pg + page; |
912 | vbi_char *acp = pg->text; |
913 | memset(pg->text, 0, sizeof(pg->text)); |
914 | #if 0 |
915 | //vbi_char c = cc->transp_space[ch >= &cc->channel[4]]; |
916 | vbi_char c = {0}; |
917 | int i,j; |
918 | for (i = 0; i < COLUMNS * ROWS; acp++, i++) |
919 | *acp = c; |
920 | |
921 | pg->dirty.y0 = 0; |
922 | pg->dirty.y1 = ROWS - 1; |
923 | pg->dirty.roll = ROWS; |
924 | #endif |
925 | } |
926 | |
927 | static const vbi_color |
928 | palette_mapping[8] = { |
929 | VBI_WHITE, VBI_GREEN, VBI_BLUE, VBI_CYAN, |
930 | VBI_RED, VBI_YELLOW, VBI_MAGENTA, VBI_BLACK |
931 | }; |
932 | |
933 | static int |
934 | row_mapping[] = { |
935 | 10, -1, 0, 1, 2, 3, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9 |
936 | }; |
937 | |
938 | // not verified means I didn't encounter the code in a |
939 | // sample stream yet |
940 | |
941 | _vbi_inline void |
942 | caption_command(vbi_decoder *vbi, struct caption *cc, |
943 | unsigned char c1, unsigned char c2, vbi_bool field2) |
944 | { |
945 | XDS_SEP_DEBUG("caption_command\n"); |
946 | cc_channel *ch; |
947 | vbi_char *c; |
948 | int chan, col, i; |
949 | int last_row; |
950 | chan = (cc->curr_chan & 4) + field2 * 2 + ((c1 >> 3) & 1); |
951 | ch = &cc->channel[chan]; |
952 | |
953 | c1 &= 7; |
954 | |
955 | if (c2 >= 0x40) { /* Preamble Address Codes 001 crrr 1ri xxxu */ |
956 | int row = row_mapping[(c1 << 1) + ((c2 >> 5) & 1)]; |
957 | int old_row = ch->row; |
958 | |
959 | if (row < 0 || !ch->mode) |
960 | return; |
961 | |
962 | ch->attr.underline = c2 & 1; |
963 | ch->attr.background = VBI_BLACK; |
964 | ch->attr.opacity = VBI_OPAQUE; |
965 | ch->attr.flash = FALSE; |
966 | |
967 | word_break(cc, ch, 1); |
968 | |
969 | if (ch->mode == MODE_ROLL_UP) { |
970 | int row1 = row - ch->roll + 1; |
971 | |
972 | if (row1 < 0) |
973 | row1 = 0; |
974 | |
975 | if (row1 != ch->row1) { |
976 | ch->row1 = row1; |
977 | erase_memory(cc, ch, ch->hidden); |
978 | erase_memory(cc, ch, ch->hidden ^ 1); |
979 | } |
980 | |
981 | set_cursor(ch, 1, ch->row1 + ch->roll - 1); |
982 | } else |
983 | set_cursor(ch, 1, row); |
984 | |
985 | if (c2 & 0x10) { |
986 | /* |
987 | col = ch->col; |
988 | |
989 | for (i = (c2 & 14) * 2; i > 0 && col < COLUMNS - 1; i--) |
990 | ch->line[col++] = cc->transp_space[chan >> 2]; |
991 | |
992 | if (col > ch->col) |
993 | ch->col = ch->col1 = col; |
994 | */ |
995 | |
996 | ch->col = (c2 & 14) * 2 + 1; |
997 | |
998 | if (old_row != ch->row) { |
999 | ch->attr.italic = FALSE; |
1000 | ch->attr.foreground = VBI_WHITE; |
1001 | } |
1002 | } else { |
1003 | // not verified |
1004 | c2 = (c2 >> 1) & 7; |
1005 | |
1006 | if (c2 < 7) { |
1007 | ch->attr.italic = FALSE; |
1008 | ch->attr.foreground = palette_mapping[c2]; |
1009 | } else { |
1010 | ch->attr.italic = TRUE; |
1011 | //ch->attr.foreground = VBI_WHITE; |
1012 | } |
1013 | } |
1014 | |
1015 | return; |
1016 | } |
1017 | |
1018 | switch (c1) { |
1019 | case 0: |
1020 | /* Optional Attributes 001 c000 010 xxxt */ |
1021 | ch->attr.opacity = (c2 & 1) ? VBI_SEMI_TRANSPARENT : VBI_OPAQUE; |
1022 | ch->attr.background = palette_mapping[(c2 >> 1) & 7]; |
1023 | //AM_DEBUG(1, "background_set %x %x %x", ch->line[ch->col].unicode, ch->line[ch->col-1].unicode, ch->line[ch->col-2].unicode); |
1024 | for (i=0; i<2 && ((ch->col - i) >= 0); i++) |
1025 | { |
1026 | if (ch->col < COLUMNS - 1) |
1027 | c = &ch->line[ch->col - i]; |
1028 | else { |
1029 | /* line break here? */ |
1030 | c = &ch->line[COLUMNS - 2 - i]; |
1031 | } |
1032 | if (c->unicode == 0x20) |
1033 | { |
1034 | c->background = ch->attr.background; |
1035 | c->opacity = ch->attr.opacity; |
1036 | } |
1037 | } |
1038 | return; |
1039 | |
1040 | case 1: |
1041 | if (c2 & 0x10) { /* Special Characters 001 c001 011 xxxx */ |
1042 | // not verified |
1043 | c2 &= 15; |
1044 | |
1045 | if (c2 == 9) { // "transparent space" |
1046 | if (ch->col < COLUMNS - 1) { |
1047 | ch->line[ch->col++] = cc->transp_space[chan >> 2]; |
1048 | ch->col1 = ch->col; |
1049 | } else |
1050 | ch->line[COLUMNS - 2] = cc->transp_space[chan >> 2]; |
1051 | // XXX boxed logic? |
1052 | } else { |
1053 | vbi_char c = ch->attr; |
1054 | |
1055 | c.unicode = vbi_caption_unicode (0x1130 | (c2 & 15), |
1056 | /* to_upper */ FALSE); |
1057 | XDS_SEP_DEBUG("vbi_caption_unicode %c\n",c.unicode); |
1058 | put_char(cc, ch, c); |
1059 | } |
1060 | } else { /* Midrow Codes 001 c001 010 xxxu */ |
1061 | // not verified |
1062 | ch->attr.flash = FALSE; |
1063 | ch->attr.underline = c2 & 1; |
1064 | |
1065 | c2 = (c2 >> 1) & 7; |
1066 | |
1067 | if (c2 < 7) { |
1068 | ch->attr.italic = FALSE; |
1069 | ch->attr.foreground = palette_mapping[c2]; |
1070 | } else { |
1071 | ch->attr.italic = TRUE; |
1072 | //ch->attr.foreground = VBI_WHITE; |
1073 | } |
1074 | } |
1075 | |
1076 | return; |
1077 | |
1078 | case 2: /* Optional Extended Characters 001 c01f 01x xxxx */ |
1079 | case 3: |
1080 | /* Send specs to the maintainer of this code */ |
1081 | { |
1082 | vbi_char c; |
1083 | |
1084 | c = ch->attr; |
1085 | c.unicode = vbi_caption_unicode((c1 << 8) | c2 | 0x1000, 0); |
1086 | |
1087 | if (c.unicode) { |
1088 | if (ch->col > 1) |
1089 | ch->col --; |
1090 | |
1091 | put_char(cc, ch, c); |
1092 | } |
1093 | } |
1094 | return; |
1095 | |
1096 | case 4: /* Misc Control Codes 001 c10f 010 xxxx */ |
1097 | case 5: /* Misc Control Codes 001 c10f 010 xxxx */ |
1098 | /* f ("field"): purpose? */ |
1099 | |
1100 | switch (c2 & 15) { |
1101 | case 0: /* Resume Caption Loading 001 c10f 010 0000 */ |
1102 | ch = switch_channel(cc, ch, chan & 3); |
1103 | |
1104 | ch->mode = MODE_POP_ON; |
1105 | |
1106 | // no? erase_memory(cc, ch); |
1107 | |
1108 | return; |
1109 | |
1110 | /* case 4: reserved */ |
1111 | |
1112 | case 5: /* Roll-Up Captions 001 c10f 010 0xxx */ |
1113 | case 6: |
1114 | case 7: |
1115 | { |
1116 | int roll = (c2 & 7) - 3; |
1117 | |
1118 | ch = switch_channel(cc, ch, chan & 3); |
1119 | |
1120 | if (ch->mode == MODE_ROLL_UP && ch->roll == roll) |
1121 | return; |
1122 | |
1123 | erase_memory(cc, ch, ch->hidden); |
1124 | erase_memory(cc, ch, ch->hidden ^ 1); |
1125 | |
1126 | ch->mode = MODE_ROLL_UP; |
1127 | ch->roll = roll; |
1128 | |
1129 | set_cursor(ch, 1, 14); |
1130 | |
1131 | ch->row1 = 14 - roll + 1; |
1132 | |
1133 | return; |
1134 | } |
1135 | |
1136 | case 9: /* Resume Direct Captioning 001 c10f 010 1001 */ |
1137 | // not verified |
1138 | ch = switch_channel(cc, ch, chan & 3); |
1139 | ch->mode = MODE_PAINT_ON; |
1140 | return; |
1141 | |
1142 | case 10: /* Text Restart 001 c10f 010 1010 */ |
1143 | // not verified |
1144 | erase_memory(cc, ch, ch->hidden); |
1145 | erase_memory(cc, ch, ch->hidden ^ 1); |
1146 | ch = switch_channel(cc, ch, chan | 4); |
1147 | set_cursor(ch, 1, 0); |
1148 | erase_memory(cc, ch, ch->hidden); |
1149 | erase_memory(cc, ch, ch->hidden ^ 1); |
1150 | return; |
1151 | |
1152 | case 11: /* Resume Text Display 001 c10f 010 1011 */ |
1153 | ch = switch_channel(cc, ch, chan | 4); |
1154 | return; |
1155 | |
1156 | case 15: /* End Of Caption 001 c10f 010 1111 */ |
1157 | ch = switch_channel(cc, ch, chan & 3); |
1158 | ch->mode = MODE_POP_ON; |
1159 | |
1160 | word_break(cc, ch, 1); |
1161 | |
1162 | ch->hidden ^= 1; |
1163 | |
1164 | render(ch->pg + (ch->hidden ^ 1), -1 /* ! */); |
1165 | |
1166 | erase_memory(cc, ch, ch->hidden); // yes? |
1167 | |
1168 | /* |
1169 | * A Preamble Address Code should follow, |
1170 | * reset to a known state to be safe. |
1171 | * Reset ch->line for new ch->hidden. |
1172 | * XXX row 0? |
1173 | */ |
1174 | set_cursor(ch, 1, ROWS - 1); |
1175 | |
1176 | return; |
1177 | |
1178 | case 8: /* Flash On 001 c10f 010 1000 */ |
1179 | // not verified |
1180 | ch->attr.flash = TRUE; |
1181 | return; |
1182 | |
1183 | case 1: /* Backspace 001 c10f 010 0001 */ |
1184 | // not verified |
1185 | if (ch->mode) { |
1186 | if (ch->col > 1) { |
1187 | if (ch->line[ch->col - 1].unicode == 0) |
1188 | return; |
1189 | ch->col --; |
1190 | } else if (ch->row > 0) { |
1191 | vbi_char *acp; |
1192 | |
1193 | ch->row --; |
1194 | ch->line = ch->pg[ch->hidden].text + ch->row * COLUMNS; |
1195 | ch->col1 = 1; |
1196 | ch->col = COLUMNS - 1; |
1197 | acp = ch->line + COLUMNS - 1; |
1198 | |
1199 | while (ch->col > 1) { |
1200 | if (acp->unicode != 0) |
1201 | break; |
1202 | |
1203 | acp --; |
1204 | ch->col --; |
1205 | } |
1206 | } else { |
1207 | return; |
1208 | } |
1209 | |
1210 | memset(&ch->line[ch->col], 0, sizeof(vbi_char)); |
1211 | if (ch->col < ch->col1) |
1212 | ch->col1 = ch->col; |
1213 | ch->update = 1; |
1214 | } |
1215 | |
1216 | return; |
1217 | |
1218 | case 13: /* Carriage Return 001 c10f 010 1101 */ |
1219 | if (ch == cc->channel + 5) |
1220 | itv_separator(vbi, cc, 0); |
1221 | |
1222 | if (!ch->mode) |
1223 | return; |
1224 | |
1225 | last_row = ch->row1 + ch->roll - 1; |
1226 | |
1227 | if (last_row > ROWS - 1) |
1228 | last_row = ROWS - 1; |
1229 | |
1230 | if (ch->row < last_row) { |
1231 | word_break(cc, ch, 1); |
1232 | set_cursor(ch, 1, ch->row + 1); |
1233 | } else { |
1234 | vbi_char *acp = &ch->pg[ch->hidden ^ (ch->mode != MODE_POP_ON)] |
1235 | .text[ch->row1 * COLUMNS]; |
1236 | |
1237 | word_break(cc, ch, 1); |
1238 | ch->update = 1; |
1239 | |
1240 | /*if (ch->mode == MODE_ROLL_UP) */ |
1241 | { |
1242 | memmove(acp, acp + COLUMNS, sizeof(*acp) * (ch->roll - 1) * COLUMNS); |
1243 | for (i = 0; i <= COLUMNS; i++) { |
1244 | memset(&ch->line[i], 0, sizeof(vbi_char)); |
1245 | } |
1246 | } |
1247 | |
1248 | if (ch->mode != MODE_POP_ON) { |
1249 | ch->update = 1; |
1250 | roll_up(ch->pg + (ch->hidden ^ 1), ch->row1, last_row); |
1251 | } |
1252 | |
1253 | ch->col1 = ch->col = 1; |
1254 | } |
1255 | ch->attr.underline = FALSE; |
1256 | ch->attr.background = VBI_BLACK; |
1257 | ch->attr.opacity = VBI_OPAQUE; |
1258 | ch->attr.flash = FALSE; |
1259 | ch->attr.italic = FALSE; |
1260 | ch->attr.foreground = VBI_WHITE; |
1261 | |
1262 | return; |
1263 | |
1264 | case 4: /* Delete To End Of Row 001 c10f 010 0100 */ |
1265 | // not verified |
1266 | if (!ch->mode) |
1267 | return; |
1268 | |
1269 | for (i = ch->col; i <= COLUMNS - 1; i++) { |
1270 | memset(&ch->line[i], 0, sizeof(vbi_char)); |
1271 | } |
1272 | |
1273 | word_break(cc, ch, 0); |
1274 | |
1275 | if (ch->mode != MODE_POP_ON) { |
1276 | ch->update = 1; |
1277 | render(ch->pg + (ch->hidden ^ 1), ch->row); |
1278 | } |
1279 | |
1280 | ch->update = 1; |
1281 | return; |
1282 | |
1283 | case 12: /* Erase Displayed Memory 001 c10f 010 1100 */ |
1284 | // s1, s4: EDM always before EOC |
1285 | /* Received in text mode */ |
1286 | /* |
1287 | if (cc->curr_chan >= 4) |
1288 | { |
1289 | for (i = 0; i < 2; i++ ) |
1290 | { |
1291 | ch = &cc->channel[i + (cc->curr_chan - 4)/2]; |
1292 | erase_memory(cc, ch, 0); |
1293 | erase_memory(cc, ch, 1); |
1294 | } |
1295 | } |
1296 | else |
1297 | { |
1298 | |
1299 | if (ch->mode != MODE_POP_ON) |
1300 | erase_memory(cc, ch, ch->hidden); |
1301 | |
1302 | erase_memory(cc, ch, ch->hidden ^ 1); |
1303 | |
1304 | |
1305 | clear(ch->pg + (ch->hidden ^ 1)); |
1306 | }*/ |
1307 | |
1308 | if (cc->curr_chan < 4) { |
1309 | //erase_memory(cc, ch, ch->hidden); |
1310 | erase_memory(cc, ch, ch->hidden ^ 1); |
1311 | clear(ch->pg + (ch->hidden ^ 1)); |
1312 | |
1313 | ch->update = 1; |
1314 | } |
1315 | return; |
1316 | case 14: /* Erase Non-Displayed Memory 001 c10f 010 1110 */ |
1317 | // not verified |
1318 | /*if (ch->mode == MODE_POP_ON)*/ |
1319 | erase_memory(cc, ch, ch->hidden); |
1320 | |
1321 | //erase_memory(cc, ch, ch->hidden ^ 1); |
1322 | //ch->update = 1; |
1323 | return; |
1324 | } |
1325 | |
1326 | return; |
1327 | |
1328 | /* case 6: reserved */ |
1329 | |
1330 | case 7: |
1331 | if (!ch->mode) |
1332 | return; |
1333 | |
1334 | switch (c2) { |
1335 | case 0x21 ... 0x23: /* Misc Control Codes, Tabs 001 c111 010 00xx */ |
1336 | // not verified |
1337 | /* |
1338 | col = ch->col; |
1339 | |
1340 | for (i = c2 & 3; i > 0 && col < COLUMNS - 1; i--) |
1341 | ch->line[col++] = cc->transp_space[chan >> 2]; |
1342 | |
1343 | if (col > ch->col) |
1344 | ch->col = ch->col1 = col;*/ |
1345 | |
1346 | ch->col += (c2 & 3); |
1347 | if (ch->col >= COLUMNS) |
1348 | ch->col = COLUMNS - 1; |
1349 | |
1350 | return; |
1351 | |
1352 | case 0x2D: /* Optional Attributes 001 c111 010 11xx */ |
1353 | // not verified |
1354 | ch->attr.opacity = VBI_TRANSPARENT_FULL; |
1355 | break; |
1356 | |
1357 | case 0x2E: /* Optional Attributes 001 c111 010 11xx */ |
1358 | case 0x2F: |
1359 | // not verified |
1360 | ch->attr.foreground = VBI_BLACK; |
1361 | ch->attr.underline = c2 & 1; |
1362 | break; |
1363 | |
1364 | default: |
1365 | return; |
1366 | } |
1367 | |
1368 | /* Optional Attributes, backspace magic */ |
1369 | |
1370 | if (ch->col > 1 && (ch->line[ch->col - 1].unicode & 0x7F) == 0x20) { |
1371 | vbi_char c = ch->attr; |
1372 | |
1373 | c.unicode = 0x0020; |
1374 | ch->line[ch->col - 1] = c; |
1375 | } |
1376 | } |
1377 | } |
1378 | |
1379 | /** |
1380 | * @internal |
1381 | * @param vbi Initialized vbi decoding context. |
1382 | * @param line ITU-R line number this data originated from. |
1383 | * @param buf Two bytes. |
1384 | * |
1385 | * Decode two bytes of Closed Caption data (Caption, XDS, ITV), |
1386 | * updating the decoder state accordingly. May send events. |
1387 | */ |
1388 | void |
1389 | vbi_decode_caption(vbi_decoder *vbi, int line, uint8_t *buf) |
1390 | { |
1391 | XDS_SEP_DEBUG("vbi_decode_caption\n"); |
1392 | struct caption *cc = &vbi->cc; |
1393 | char c1 = buf[0] & 0x7F; |
1394 | int field2 = 1, i; |
1395 | |
1396 | pthread_mutex_lock(&cc->mutex); |
1397 | //AM_DEBUG(1, "vbi_data: line: %d %x %x", line, buf[0], buf[1]); |
1398 | |
1399 | if (line == 21) { |
1400 | cc->curr_chan = cc->curr_chan_f1; |
1401 | memcpy(cc->last, cc->last_f1, sizeof(cc->last)); |
1402 | } else { |
1403 | cc->curr_chan = cc->curr_chan_f2; |
1404 | memcpy(cc->last, cc->last_f2, sizeof(cc->last)); |
1405 | } |
1406 | |
1407 | switch (line) { |
1408 | case 21: /* NTSC */ |
1409 | case 22: /* PAL */ |
1410 | field2 = 0; |
1411 | break; |
1412 | |
1413 | case 335: /* PAL, hardly XDS */ |
1414 | break; |
1415 | |
1416 | case 284: /* NTSC */ |
1417 | CC_DUMP( |
1418 | putchar(_vbi_to_ascii (buf[0])); |
1419 | putchar(_vbi_to_ascii (buf[1])); |
1420 | fflush(stdout); |
1421 | ) |
1422 | |
1423 | if (vbi_unpar8 (buf[0]) >= 0) { //vbi_unpar8 (buf[0]) >= 0 |
1424 | if (c1 == 0) { |
1425 | goto finish; |
1426 | } else if (c1 <= 0x0F) { |
1427 | xds_separator(vbi, buf); |
1428 | cc->xds = (c1 != XDS_END); |
1429 | goto finish; |
1430 | } else if (c1 <= 0x1F) { |
1431 | cc->xds = FALSE; |
1432 | } else if (cc->xds) { |
1433 | xds_separator(vbi, buf); |
1434 | goto finish; |
1435 | } |
1436 | } else if (cc->xds) { |
1437 | xds_separator(vbi, buf); |
1438 | goto finish; |
1439 | } |
1440 | |
1441 | break; |
1442 | |
1443 | default: |
1444 | goto finish; |
1445 | } |
1446 | |
1447 | //if ( (buf[0]) < 0) { //vbi_unpar8 (buf[0]) < 0 |
1448 | if ( vbi_unpar8 (buf[0]) < 0) { // |
1449 | #if 0 |
1450 | c1 = 127; |
1451 | buf[0] = c1; /* traditional 'bad' glyph, ccfont has */ |
1452 | buf[1] = c1; /* room, design a special glyph? */ |
1453 | #else |
1454 | goto finish; |
1455 | #endif |
1456 | } |
1457 | |
1458 | CC_DUMP( |
1459 | putchar(_vbi_to_ascii (buf[0])); |
1460 | putchar(_vbi_to_ascii (buf[1])); |
1461 | fflush(stdout); |
1462 | ) |
1463 | |
1464 | switch (c1) { |
1465 | cc_channel *ch; |
1466 | vbi_char c; |
1467 | |
1468 | case 0x01 ... 0x0F: |
1469 | /*if (!field2)*/ |
1470 | cc->last[0] = 0; |
1471 | break; /* XDS field 1?? */ |
1472 | |
1473 | case 0x10 ... 0x1F: |
1474 | //if ( (buf[1]) >= 0) { // vbi_unpar8 (buf[1]) |
1475 | if ( vbi_unpar8 (buf[1]) >= 0) { |
1476 | if (/*!field2 |
1477 | &&*/ buf[0] == cc->last[0] |
1478 | && buf[1] == cc->last[1]) { |
1479 | /* cmd repetition F1: already executed */ |
1480 | cc->last[0] = 0; /* one rep */ |
1481 | break; |
1482 | } |
1483 | |
1484 | caption_command(vbi, cc, c1, buf[1] & 0x7F, field2); |
1485 | |
1486 | /*if (!field2)*/ { |
1487 | cc->last[0] = buf[0]; |
1488 | cc->last[1] = buf[1]; |
1489 | } |
1490 | } else /*if (!field2)*/ |
1491 | cc->last[0] = 0; |
1492 | |
1493 | break; |
1494 | |
1495 | default: |
1496 | CC_TEXT_DUMP( |
1497 | putchar(_vbi_to_ascii (buf[0])); |
1498 | putchar(_vbi_to_ascii (buf[1])); |
1499 | fflush(stdout); |
1500 | ) |
1501 | #if 0 |
1502 | AM_DEBUG(1, "text value: %c %c %x %x", _vbi_to_ascii(buf[0]), |
1503 | _vbi_to_ascii(buf[1]), buf[0], buf[1]); |
1504 | #endif |
1505 | |
1506 | ch = &cc->channel[(cc->curr_chan & 5) + field2 * 2]; |
1507 | |
1508 | if (buf[0] == 0x80 && buf[1] == 0x80) { |
1509 | if (ch->mode) { |
1510 | if (ch->nul_ct == 2) |
1511 | word_break(cc, ch, 1); |
1512 | ch->nul_ct += 2; |
1513 | } |
1514 | |
1515 | break; |
1516 | } |
1517 | |
1518 | /*if (!field2)*/ |
1519 | cc->last[0] = 0; |
1520 | |
1521 | ch->nul_ct = 0; |
1522 | |
1523 | if (!ch->mode) |
1524 | break; |
1525 | |
1526 | ch->time = vbi->time; /* activity measure */ |
1527 | |
1528 | c = ch->attr; |
1529 | |
1530 | for (i = 0; i < 2; i++) { |
1531 | //char ci = (buf[i]) & 0x7F; /* 127 if bad */ //vbi_unpar8 (buf[i]) |
1532 | char ci = vbi_unpar8 (buf[i]) & 0x7F; /* 127 if bad */ |
1533 | if (ci <= 0x1F) /* 0x00 no char, 0x01 ... 0x1F invalid */ |
1534 | continue; |
1535 | |
1536 | if (ch == cc->channel + 5) // 'T2' |
1537 | itv_separator(vbi, cc, ci); |
1538 | |
1539 | c.unicode = vbi_caption_unicode(ci, /* to_upper */ FALSE); |
1540 | |
1541 | put_char(cc, ch, c); |
1542 | } |
1543 | } |
1544 | for (i=0; i<8; i++) |
1545 | { |
1546 | if (cc->channel[i].update == 1) |
1547 | update(&cc->channel[i]); |
1548 | cc->channel[i].update = 0; |
1549 | } |
1550 | |
1551 | if (line == 21) { |
1552 | cc->curr_chan_f1 = cc->curr_chan; |
1553 | memcpy(cc->last_f1, cc->last, sizeof(cc->last)); |
1554 | } else { |
1555 | cc->curr_chan_f2 = cc->curr_chan; |
1556 | memcpy(cc->last_f2, cc->last, sizeof(cc->last)); |
1557 | } |
1558 | |
1559 | finish: |
1560 | pthread_mutex_unlock(&cc->mutex); |
1561 | } |
1562 | |
1563 | /** |
1564 | * @internal |
1565 | * @param vbi Initialized vbi decoding context. |
1566 | * |
1567 | * This function must be called after desynchronisation |
1568 | * has been detected (i. e. vbi data has been lost) |
1569 | * to reset the Closed Caption decoder. |
1570 | */ |
1571 | void |
1572 | vbi_caption_desync(vbi_decoder *vbi) |
1573 | { |
1574 | struct caption *cc = &vbi->cc; |
1575 | |
1576 | /* cc->curr_chan = 8; *//* garbage */ |
1577 | |
1578 | /* cc->xds = FALSE; */ |
1579 | |
1580 | if (cc->curr_sp) { |
1581 | memset(cc->curr_sp, 0, sizeof(*(cc->curr_sp))); |
1582 | cc->curr_sp = NULL; |
1583 | } |
1584 | |
1585 | cc->itv_count = 0; |
1586 | } |
1587 | |
1588 | /** |
1589 | * @internal |
1590 | * @param vbi Initialized vbi decoding context. |
1591 | * |
1592 | * This function must be called after a channel switch, |
1593 | * to reset the Closed Caption decoder. |
1594 | */ |
1595 | void |
1596 | vbi_caption_channel_switched(vbi_decoder *vbi) |
1597 | { |
1598 | struct caption *cc = &vbi->cc; |
1599 | cc_channel *ch; |
1600 | int i; |
1601 | |
1602 | for (i = 0; i < 9; i++) { |
1603 | ch = &cc->channel[i]; |
1604 | |
1605 | if (i < 4) { |
1606 | ch->mode = MODE_NONE; // MODE_ROLL_UP; |
1607 | ch->row = ROWS - 1; |
1608 | ch->row1 = ROWS - 3; |
1609 | ch->roll = 3; |
1610 | } else { |
1611 | ch->mode = MODE_TEXT; |
1612 | ch->row1 = ch->row = 0; |
1613 | ch->roll = ROWS; |
1614 | } |
1615 | |
1616 | ch->attr.opacity = VBI_OPAQUE; |
1617 | ch->attr.foreground = VBI_WHITE; |
1618 | ch->attr.background = VBI_BLACK; |
1619 | |
1620 | set_cursor(ch, 1, ch->row); |
1621 | |
1622 | ch->time = 0.0; |
1623 | |
1624 | ch->hidden = 0; |
1625 | |
1626 | ch->pg[0].dirty.y0 = 0; |
1627 | ch->pg[0].dirty.y1 = ROWS - 1; |
1628 | ch->pg[0].dirty.roll = 0; |
1629 | |
1630 | erase_memory(cc, ch, 0); |
1631 | |
1632 | memcpy(&ch->pg[1], &ch->pg[0], sizeof(ch->pg[1])); |
1633 | } |
1634 | |
1635 | cc->xds = FALSE; |
1636 | |
1637 | memset(&cc->sub_packet, 0, sizeof(cc->sub_packet)); |
1638 | |
1639 | cc->info_cycle[0] = 0; |
1640 | cc->info_cycle[1] = 0; |
1641 | |
1642 | vbi_caption_desync(vbi); |
1643 | } |
1644 | |
1645 | static vbi_rgba |
1646 | default_color_map[8] = { |
1647 | VBI_RGBA(0x00, 0x00, 0x00), VBI_RGBA(0xFF, 0x00, 0x00), |
1648 | VBI_RGBA(0x00, 0xFF, 0x00), VBI_RGBA(0xFF, 0xFF, 0x00), |
1649 | VBI_RGBA(0x00, 0x00, 0xFF), VBI_RGBA(0xFF, 0x00, 0xFF), |
1650 | VBI_RGBA(0x00, 0xFF, 0xFF), VBI_RGBA(0xFF, 0xFF, 0xFF) |
1651 | }; |
1652 | |
1653 | /** |
1654 | * @internal |
1655 | * @param vbi Initialized vbi decoding context. |
1656 | * |
1657 | * After the client changed text brightness and saturation |
1658 | * this function adjusts the Closed Caption color palette. |
1659 | */ |
1660 | void |
1661 | vbi_caption_color_level(vbi_decoder *vbi) |
1662 | { |
1663 | int i; |
1664 | |
1665 | vbi_transp_colormap(vbi, vbi->cc.channel[0].pg[0].color_map, |
1666 | default_color_map, 8); |
1667 | |
1668 | for (i = 1; i < 16; i++) |
1669 | memcpy(vbi->cc.channel[i >> 1].pg[i & 1].color_map, |
1670 | vbi->cc.channel[0].pg[0].color_map, |
1671 | sizeof(default_color_map)); |
1672 | } |
1673 | |
1674 | /** |
1675 | * @internal |
1676 | * @param vbi VBI decoding context. |
1677 | * |
1678 | * This function is called during @a vbi destruction |
1679 | * to destroy Closed Caption subset of @a vbi. |
1680 | */ |
1681 | void |
1682 | vbi_caption_destroy(vbi_decoder *vbi) |
1683 | { |
1684 | pthread_mutex_destroy(&vbi->cc.mutex); |
1685 | } |
1686 | |
1687 | /** |
1688 | * @internal |
1689 | * @param vbi VBI decoding context. |
1690 | * |
1691 | * This function is called during @a vbi initialization |
1692 | * to initialize the Closed Caption subset of @a vbi. |
1693 | */ |
1694 | void |
1695 | vbi_caption_init(vbi_decoder *vbi) |
1696 | { |
1697 | struct caption *cc = &vbi->cc; |
1698 | cc_channel *ch; |
1699 | int i; |
1700 | |
1701 | memset(cc, 0, sizeof(struct caption)); |
1702 | |
1703 | pthread_mutex_init(&cc->mutex, NULL); |
1704 | |
1705 | for (i = 0; i < 9; i++) { |
1706 | ch = &cc->channel[i]; |
1707 | |
1708 | ch->pg[0].vbi = vbi; |
1709 | |
1710 | ch->pg[0].pgno = i + 1; |
1711 | ch->pg[0].subno = 0; |
1712 | |
1713 | ch->pg[0].rows = ROWS; |
1714 | ch->pg[0].columns = COLUMNS; |
1715 | |
1716 | ch->pg[0].screen_color = 0; |
1717 | ch->pg[0].screen_opacity = (i < 4) ? VBI_TRANSPARENT_SPACE : VBI_OPAQUE; |
1718 | |
1719 | ch->pg[0].font[0] = vbi_font_descriptors; /* English */ |
1720 | ch->pg[0].font[1] = vbi_font_descriptors; |
1721 | |
1722 | memcpy(&ch->pg[1], &ch->pg[0], sizeof(ch->pg[1])); |
1723 | } |
1724 | |
1725 | for (i = 0; i < 2; i++) { |
1726 | cc->transp_space[i].foreground = VBI_WHITE; |
1727 | cc->transp_space[i].background = VBI_BLACK; |
1728 | cc->transp_space[i].unicode = 0x0020; |
1729 | } |
1730 | |
1731 | cc->transp_space[0].opacity = VBI_TRANSPARENT_SPACE; |
1732 | cc->transp_space[1].opacity = VBI_OPAQUE; |
1733 | |
1734 | vbi_caption_channel_switched(vbi); |
1735 | |
1736 | vbi_caption_color_level(vbi); |
1737 | } |
1738 | |
1739 | /** |
1740 | * @param vbi Initialized vbi decoding context. |
1741 | * @param pg Place to store the formatted page. |
1742 | * @param pgno Page number 1 ... 8 of the page to fetch, see vbi_pgno. |
1743 | * @param reset @c TRUE resets the vbi_page dirty fields in cache after |
1744 | * fetching. Pass @c FALSE only if you plan to call this function again |
1745 | * to update other displays. |
1746 | * |
1747 | * Fetches a Closed Caption page designated by @a pgno from the cache, |
1748 | * formats and stores it in @a pg. CC pages are transmitted basically in |
1749 | * two modes: at once and character by character ("roll-up" mode). |
1750 | * Either way you get a snapshot of the page as it should appear on |
1751 | * screen at present. With vbi_event_handler_add() you can request a |
1752 | * @c VBI_EVENT_CAPTION event to be notified about pending changes |
1753 | * (in case of "roll-up" mode that is with each new word received) |
1754 | * and the vbi_page->dirty fields will mark the lines actually in |
1755 | * need of updates, to speed up rendering. |
1756 | * |
1757 | * Although safe to do, this function is not supposed to be |
1758 | * called from an event handler, since rendering may block decoding |
1759 | * for extended periods of time. |
1760 | * |
1761 | * @return |
1762 | * @c FALSE if some error occured. |
1763 | */ |
1764 | vbi_bool |
1765 | vbi_fetch_cc_page(vbi_decoder *vbi, vbi_page *pg, vbi_pgno pgno, vbi_bool reset) |
1766 | { |
1767 | cc_channel *ch = vbi->cc.channel + ((pgno - 1) & 7); |
1768 | vbi_page *spg; |
1769 | |
1770 | reset = reset; |
1771 | |
1772 | if (pgno < 1 || pgno > 8) |
1773 | return FALSE; |
1774 | |
1775 | pthread_mutex_lock(&vbi->cc.mutex); |
1776 | |
1777 | spg = ch->pg + (ch->hidden ^ 1); |
1778 | |
1779 | memcpy(pg, spg, sizeof(*pg)); /* shortcut? */ |
1780 | |
1781 | spg->dirty.y0 = ROWS; |
1782 | spg->dirty.y1 = -1; |
1783 | spg->dirty.roll = 0; |
1784 | |
1785 | pthread_mutex_unlock(&vbi->cc.mutex); |
1786 | |
1787 | return 1; |
1788 | } |
1789 | |
1790 | void vbi_refresh_cc(vbi_decoder *vbi) |
1791 | { |
1792 | cc_channel *ch; |
1793 | vbi_page *spg; |
1794 | int i, j; |
1795 | int flash_flag = 0; |
1796 | vbi_event event; |
1797 | pthread_mutex_lock(&vbi->cc.mutex); |
1798 | for (i = 0; i < 8; i++) |
1799 | { |
1800 | flash_flag = 0; |
1801 | ch = &vbi->cc.channel[i]; |
1802 | spg = ch->pg + (ch->hidden ^ 1); |
1803 | for (j = 0; j < sizeof(spg->text)/sizeof(vbi_char); j++) |
1804 | { |
1805 | if (spg->text[j].flash) |
1806 | { |
1807 | flash_flag = 1; |
1808 | break; |
1809 | } |
1810 | } |
1811 | if (flash_flag) |
1812 | { |
1813 | update(ch); |
1814 | } |
1815 | } |
1816 | pthread_mutex_unlock(&vbi->cc.mutex); |
1817 | } |
1818 | |
1819 | /* |
1820 | Local variables: |
1821 | c-set-style: K&R |
1822 | c-basic-offset: 8 |
1823 | End: |
1824 | */ |
1825 |