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