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