summaryrefslogtreecommitdiff
path: root/src/caption.c (plain)
blob: d21b0c456f49a56ff095b99abccb6fabde317d8e
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 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
614static void
615xds_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
718static void
719itv_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
750static void
751render(vbi_page *pg)
752{
753 vbi_event event;
754#if 0
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#endif
766 event.type = VBI_EVENT_CAPTION;
767 event.ev.caption.pgno = pg->pgno;
768
769 caption_send_event(pg->vbi, &event);
770}
771
772#if 0
773static void
774clear(vbi_page *pg)
775{
776 vbi_event event;
777
778 pg->dirty.y0 = 0;
779 pg->dirty.y1 = ROWS - 1;
780 pg->dirty.roll = -ROWS;
781
782 event.type = VBI_EVENT_CAPTION;
783 event.ev.caption.pgno = pg->pgno;
784
785 caption_send_event(pg->vbi, &event);
786}
787
788static void
789roll_up(vbi_page *pg, int first_row, int last_row)
790{
791 vbi_event event;
792
793 if (pg->dirty.roll != 0 || pg->dirty.y0 <= pg->dirty.y1) {
794 /* not fetched since last update, redraw all */
795 pg->dirty.roll = 0;
796 pg->dirty.y0 = MIN(first_row, pg->dirty.y0);
797 pg->dirty.y1 = MAX(last_row, pg->dirty.y1);
798 } else {
799 pg->dirty.roll = -1;
800 pg->dirty.y0 = first_row;
801 pg->dirty.y1 = last_row;
802 }
803
804 event.type = VBI_EVENT_CAPTION;
805 event.ev.caption.pgno = pg->pgno;
806
807 caption_send_event(pg->vbi, &event);
808}
809#endif
810
811static inline void
812update_all(cc_channel *ch)
813{
814 memcpy(&ch->pg[ch->hidden ^ 1].text, &ch->pg[ch->hidden].text, ROWS * COLUMNS * sizeof(vbi_char));
815 ch->update_flag = 1;
816 ch->update_all_flag = 0;
817}
818
819static inline void
820update_line(cc_channel *ch)
821{
822 vbi_char *acp;
823
824 if (ch->update_all_flag) {
825 update_all(ch);
826 return;
827 }
828
829 acp = ch->line - ch->pg[ch->hidden].text + ch->pg[ch->hidden ^ 1].text;
830
831 memcpy(acp, ch->line, sizeof(vbi_char) * COLUMNS);
832 ch->update_flag = 1;
833}
834
835static void
836word_break(struct caption *cc, cc_channel *ch, int upd)
837{
838 cc = cc;
839
840 /*
841 * Add a leading and trailing space.
842 */
843#if 0
844 if (ch->col > ch->col1) {
845 vbi_char c = ch->line[ch->col1];
846
847 if ((c.unicode & 0x7F) != 0x20
848 && ch->line[ch->col1 - 1].opacity == VBI_TRANSPARENT_SPACE) {
849 c.unicode = 0x20;
850 ch->line[ch->col1 - 1] = c;
851 }
852
853 c = ch->line[ch->col - 1];
854
855 if ((c.unicode & 0x7F) != 0x20
856 && ch->line[ch->col].opacity == VBI_TRANSPARENT_SPACE) {
857 c.unicode = 0x20;
858 ch->line[ch->col] = c;
859 }
860 }
861#endif
862 if (!upd || ch->mode == MODE_POP_ON)
863 return;
864
865 /*
866 * NB we render only at spaces (end of word) and
867 * before cursor motions and mode switching, to keep the
868 * drawing efforts (scaling etc) at a minimum. update()
869 * for double buffering at word granularity.
870 *
871 * XXX should not render if space follows space,
872 * but force in long words.
873 */
874 update_line(ch);
875}
876
877static void
878clear_roll(cc_channel *ch)
879{
880 int i;
881
882 for (i = 0; i < ROWS; i ++) {
883 if ((i >= ch->row1) && (i < ch->row1 + ch->roll))
884 continue;
885
886 memset(ch->pg[ch->hidden].text + i * COLUMNS, 0, sizeof(vbi_char) * COLUMNS);
887 }
888}
889
890static void
891roll_up (cc_channel *ch, int roll)
892{
893 vbi_char *acp = ch->pg[ch->hidden].text;
894
895 memmove(acp, acp + roll * COLUMNS, (ROWS - roll) * COLUMNS * sizeof(vbi_char));
896 memset(acp + (ROWS - roll) * COLUMNS, 0, roll * COLUMNS * sizeof(vbi_char));
897
898 ch->row1 -= roll;
899 ch->row -= roll;
900
901 if (ch->row1 < 0)
902 ch->row1 = 0;
903 if (ch->row < 0)
904 ch->row = 0;
905}
906
907static inline void
908set_cursor(cc_channel *ch, int col, int row)
909{
910 if (row < ch->row1) {
911 ch->row1 = row;
912 clear_roll(ch);
913 update_all(ch);
914 } else if (row >= ch->row1 + ch->roll) {
915 ch->row1 = row - ch->roll + 1;
916 clear_roll(ch);
917 update_all(ch);
918 }
919
920 ch->col = ch->col1 = col;
921 ch->row = row;
922 ch->line = ch->pg[ch->hidden].text + row * COLUMNS;
923}
924
925static void
926set_char(cc_channel *ch, int col, vbi_char c)
927{
928 if (!ch->pos_flag)
929 return;
930
931 ch->line[col] = c;
932 ch->edm_flag = 0;
933}
934
935static void
936put_char(struct caption *cc, cc_channel *ch, vbi_char c)
937{
938 /* c.foreground = rand() & 7; */
939 /* c.background = rand() & 7; */
940
941 if (ch->col < COLUMNS - 1) {
942 set_char(ch, ch->col, c);
943 ch->col ++;
944 } else {
945 /* line break here? */
946 set_char(ch, COLUMNS - 2, c);
947 }
948
949 if (ch->mode == MODE_POP_ON)
950 return;
951
952 update_line(ch);
953}
954
955static inline cc_channel *
956switch_channel(struct caption *cc, cc_channel *ch, int new_chan)
957{
958 word_break(cc, ch, 1); // we leave for a number of frames
959
960 return &cc->channel[cc->curr_chan = new_chan];
961}
962
963static void
964erase_memory(struct caption *cc, cc_channel *ch, int page)
965{
966 vbi_page *pg = ch->pg + page;
967
968 memset(pg->text, 0, ROWS * COLUMNS * sizeof(vbi_char));
969 ch->update_all_flag = 1;
970}
971
972static const vbi_color
973palette_mapping[8] = {
974 VBI_WHITE, VBI_GREEN, VBI_BLUE, VBI_CYAN,
975 VBI_RED, VBI_YELLOW, VBI_MAGENTA, VBI_BLACK
976};
977
978static int
979row_mapping[] = {
980 10, -1, 0, 1, 2, 3, 11, 12, 13, 14, 4, 5, 6, 7, 8, 9
981};
982
983// not verified means I didn't encounter the code in a
984// sample stream yet
985
986_vbi_inline void
987caption_command(vbi_decoder *vbi, struct caption *cc,
988 unsigned char c1, unsigned char c2, vbi_bool field2)
989{
990 XDS_SEP_DEBUG("caption_command\n");
991 cc_channel *ch;
992 vbi_char *c;
993 int chan, col, i;
994 int last_row;
995 chan = (cc->curr_chan & 4) + field2 * 2 + ((c1 >> 3) & 1);
996 ch = &cc->channel[chan];
997
998 c1 &= 7;
999
1000 if (c2 >= 0x40) { /* Preamble Address Codes 001 crrr 1ri xxxu */
1001 int row = row_mapping[(c1 << 1) + ((c2 >> 5) & 1)];
1002 int old_row = ch->row;
1003
1004 if (row < 0 || !ch->mode)
1005 return;
1006
1007 ch->pos_flag = 1;
1008
1009 ch->attr.underline = c2 & 1;
1010 ch->attr.background = VBI_BLACK;
1011 ch->attr.opacity = VBI_OPAQUE;
1012 ch->attr.flash = FALSE;
1013
1014 word_break(cc, ch, 1);
1015
1016 if (ch->mode == MODE_ROLL_UP) {
1017 int row1 = row - ch->roll + 1;
1018 int roll;
1019
1020 if (row1 < 0)
1021 row1 = 0;
1022
1023 roll = ch->row1 - row1;
1024 if (roll > 0) {
1025 roll_up(ch, ch->row1 - row1);
1026 update_all(ch);
1027 }
1028 }
1029
1030 set_cursor(ch, 1, row);
1031
1032 if (c2 & 0x10) {
1033 /*
1034 col = ch->col;
1035
1036 for (i = (c2 & 14) * 2; i > 0 && col < COLUMNS - 1; i--)
1037 ch->line[col++] = cc->transp_space[chan >> 2];
1038
1039 if (col > ch->col)
1040 ch->col = ch->col1 = col;
1041 */
1042
1043 ch->col = (c2 & 14) * 2 + 1;
1044
1045 if (old_row != ch->row) {
1046 ch->attr.italic = FALSE;
1047 ch->attr.foreground = VBI_WHITE;
1048 }
1049 } else {
1050// not verified
1051 c2 = (c2 >> 1) & 7;
1052
1053 if (c2 < 7) {
1054 ch->attr.italic = FALSE;
1055 ch->attr.foreground = palette_mapping[c2];
1056 } else {
1057 ch->attr.italic = TRUE;
1058 ch->attr.foreground = VBI_WHITE;
1059 }
1060 }
1061
1062 return;
1063 }
1064
1065 switch (c1) {
1066 case 0:
1067 /* Optional Attributes 001 c000 010 xxxt */
1068 ch->attr.opacity = (c2 & 1) ? VBI_SEMI_TRANSPARENT : VBI_OPAQUE;
1069 ch->attr.background = palette_mapping[(c2 >> 1) & 7];
1070
1071 for (i = 0; i < 2; i ++) {
1072 int col = ch->col - i;
1073
1074 if ((col > 0) && (col < COLUMNS - 1)) {
1075 vbi_char c = ch->line[col];
1076
1077 if (c.unicode == 0x20) {
1078 c.background = ch->attr.background;
1079 c.opacity = ch->attr.opacity;
1080 set_char(ch, col, c);
1081 }
1082 }
1083 }
1084 return;
1085
1086 case 1:
1087 if (c2 & 0x10) { /* Special Characters 001 c001 011 xxxx */
1088// not verified
1089 c2 &= 15;
1090
1091 if (c2 == 9) { // "transparent space"
1092 if (ch->col < COLUMNS - 1) {
1093 set_char(ch, ch->col++, cc->transp_space[chan >> 2]);
1094 ch->col1 = ch->col;
1095 } else {
1096 set_char(ch, COLUMNS - 2, cc->transp_space[chan >> 2]);
1097 }
1098
1099 } else {
1100 vbi_char c = ch->attr;
1101
1102 c.unicode = vbi_caption_unicode (0x1130 | (c2 & 15),
1103 /* to_upper */ FALSE);
1104 XDS_SEP_DEBUG("vbi_caption_unicode %c\n",c.unicode);
1105 put_char(cc, ch, c);
1106 }
1107 } else { /* Midrow Codes 001 c001 010 xxxu */
1108// not verified
1109 ch->attr.flash = FALSE;
1110 ch->attr.underline = c2 & 1;
1111
1112 c2 = (c2 >> 1) & 7;
1113
1114 if (c2 < 7) {
1115 ch->attr.italic = FALSE;
1116 ch->attr.foreground = palette_mapping[c2];
1117 } else {
1118 ch->attr.italic = TRUE;
1119 }
1120 }
1121
1122 return;
1123
1124 case 2: /* Optional Extended Characters 001 c01f 01x xxxx */
1125 case 3:
1126 /* Send specs to the maintainer of this code */
1127 {
1128 vbi_char c;
1129
1130 c = ch->attr;
1131 c.unicode = vbi_caption_unicode((c1 << 8) | c2 | 0x1000, 0);
1132
1133 if (c.unicode) {
1134 if (ch->col > 1)
1135 ch->col --;
1136
1137 put_char(cc, ch, c);
1138 }
1139 }
1140 return;
1141
1142 case 4: /* Misc Control Codes 001 c10f 010 xxxx */
1143 case 5: /* Misc Control Codes 001 c10f 010 xxxx */
1144 /* f ("field"): purpose? */
1145
1146 switch (c2 & 15) {
1147 case 0: /* Resume Caption Loading 001 c10f 010 0000 */
1148 ch = switch_channel(cc, ch, chan & 3);
1149
1150 ch->mode = MODE_POP_ON;
1151
1152// no? erase_memory(cc, ch);
1153
1154 return;
1155
1156 /* case 4: reserved */
1157
1158 case 5: /* Roll-Up Captions 001 c10f 010 0xxx */
1159 case 6:
1160 case 7:
1161 {
1162 int roll = (c2 & 7) - 3;
1163
1164 ch = switch_channel(cc, ch, chan & 3);
1165
1166 if (ch->mode == MODE_ROLL_UP && ch->roll == roll && !ch->edm_flag)
1167 return;
1168
1169 if (ch->edm_flag) {
1170 erase_memory(cc, ch, ch->hidden);
1171 erase_memory(cc, ch, ch->hidden ^ 1);
1172 ch->update_flag = 1;
1173 }
1174
1175 ch->mode = MODE_ROLL_UP;
1176 ch->roll = roll;
1177
1178 return;
1179 }
1180
1181 case 9: /* Resume Direct Captioning 001 c10f 010 1001 */
1182// not verified
1183 ch = switch_channel(cc, ch, chan & 3);
1184 ch->mode = MODE_PAINT_ON;
1185 return;
1186
1187 case 10: /* Text Restart 001 c10f 010 1010 */
1188// not verified
1189 erase_memory(cc, ch, ch->hidden);
1190 erase_memory(cc, ch, ch->hidden ^ 1);
1191 ch = switch_channel(cc, ch, chan | 4);
1192 set_cursor(ch, 1, 0);
1193 erase_memory(cc, ch, ch->hidden);
1194 erase_memory(cc, ch, ch->hidden ^ 1);
1195 return;
1196
1197 case 11: /* Resume Text Display 001 c10f 010 1011 */
1198 ch = switch_channel(cc, ch, chan | 4);
1199 return;
1200
1201 case 15: /* End Of Caption 001 c10f 010 1111 */
1202 ch = switch_channel(cc, ch, chan & 3);
1203 ch->mode = MODE_POP_ON;
1204
1205 word_break(cc, ch, 1);
1206
1207 ch->hidden ^= 1;
1208
1209 erase_memory(cc, ch, ch->hidden); // yes?
1210
1211 ch->update_flag = 1;
1212
1213 /*
1214 * A Preamble Address Code should follow,
1215 * reset to a known state to be safe.
1216 * Reset ch->line for new ch->hidden.
1217 * XXX row 0?
1218 */
1219 set_cursor(ch, 1, ROWS - 1);
1220
1221 return;
1222
1223 case 8: /* Flash On 001 c10f 010 1000 */
1224// not verified
1225 ch->attr.flash = TRUE;
1226 return;
1227
1228 case 1: /* Backspace 001 c10f 010 0001 */
1229// not verified
1230 if (ch->mode) {
1231 if (ch->col > 1) {
1232 if (ch->line[ch->col - 1].unicode == 0)
1233 break;
1234 ch->col --;
1235 } else if (ch->row > 0) {
1236 vbi_char *acp;
1237
1238 ch->row --;
1239 ch->line = ch->pg[ch->hidden].text + ch->row * COLUMNS;
1240 ch->col1 = 1;
1241 ch->col = COLUMNS - 1;
1242 acp = ch->line + COLUMNS - 1;
1243
1244 while (ch->col > 1) {
1245 if (acp->unicode != 0)
1246 break;
1247
1248 acp --;
1249 ch->col --;
1250 }
1251 } else {
1252 break;
1253 }
1254
1255
1256 memset(&ch->line[ch->col], 0, sizeof(vbi_char));
1257 if (ch->col < ch->col1)
1258 ch->col1 = ch->col;
1259
1260 update_line(ch);
1261 }
1262
1263 return;
1264
1265 case 13: /* Carriage Return 001 c10f 010 1101 */
1266 if (ch == cc->channel + 5)
1267 itv_separator(vbi, cc, 0);
1268
1269 if ((ch->row >= ROWS - 1) && (ch->mode == MODE_ROLL_UP)) {
1270 roll_up(ch, 1);
1271 set_cursor(ch, 1, ROWS - 1);
1272 update_all(ch);
1273 } else if (ch->row >= ROWS - 1) {
1274 set_cursor(ch, 1, ROWS - 1);
1275 if (ch->mode != MODE_POP_ON)
1276 update_line(ch);
1277 } else {
1278 set_cursor(ch, 1, ch->row + 1);
1279 if (ch->mode != MODE_POP_ON)
1280 update_line(ch);
1281 }
1282
1283 ch->attr.underline = FALSE;
1284 ch->attr.background = VBI_BLACK;
1285 ch->attr.opacity = VBI_OPAQUE;
1286 ch->attr.flash = FALSE;
1287 ch->attr.italic = FALSE;
1288 ch->attr.foreground = VBI_WHITE;
1289
1290 return;
1291
1292 case 4: /* Delete To End Of Row 001 c10f 010 0100 */
1293// not verified
1294 if (!ch->mode)
1295 return;
1296
1297 for (i = ch->col; i <= COLUMNS - 1; i++) {
1298 memset(&ch->line[i], 0, sizeof(vbi_char));
1299 }
1300
1301 word_break(cc, ch, 0);
1302
1303 if (ch->mode != MODE_POP_ON) {
1304 update_line(ch);
1305 }
1306
1307 return;
1308
1309 case 12: /* Erase Displayed Memory 001 c10f 010 1100 */
1310// s1, s4: EDM always before EOC
1311 /* Received in text mode */
1312 /*
1313 if (cc->curr_chan >= 4)
1314 {
1315 for (i = 0; i < 2; i++ )
1316 {
1317 ch = &cc->channel[i + (cc->curr_chan - 4)/2];
1318 erase_memory(cc, ch, 0);
1319 erase_memory(cc, ch, 1);
1320 }
1321 }
1322 else
1323 {
1324
1325 if (ch->mode != MODE_POP_ON)
1326 erase_memory(cc, ch, ch->hidden);
1327
1328 erase_memory(cc, ch, ch->hidden ^ 1);
1329
1330
1331 clear(ch->pg + (ch->hidden ^ 1));
1332 }*/
1333
1334 if (cc->curr_chan < 4) {
1335 erase_memory(cc, ch, ch->hidden ^ 1);
1336 ch->update_flag = 1;
1337 ch->edm_flag = 1;
1338 }
1339 return;
1340 case 14: /* Erase Non-Displayed Memory 001 c10f 010 1110 */
1341// not verified
1342 erase_memory(cc, ch, ch->hidden);
1343 return;
1344 }
1345
1346 return;
1347
1348 /* case 6: reserved */
1349
1350 case 7:
1351 if (!ch->mode)
1352 return;
1353
1354 switch (c2) {
1355 case 0x21 ... 0x23: /* Misc Control Codes, Tabs 001 c111 010 00xx */
1356// not verified
1357 /*
1358 col = ch->col;
1359
1360 for (i = c2 & 3; i > 0 && col < COLUMNS - 1; i--)
1361 ch->line[col++] = cc->transp_space[chan >> 2];
1362
1363 if (col > ch->col)
1364 ch->col = ch->col1 = col;*/
1365
1366 ch->col += (c2 & 3);
1367 if (ch->col >= COLUMNS)
1368 ch->col = COLUMNS - 1;
1369
1370 return;
1371
1372 case 0x2D: /* Optional Attributes 001 c111 010 11xx */
1373// not verified
1374 ch->attr.opacity = VBI_TRANSPARENT_FULL;
1375 break;
1376
1377 case 0x2E: /* Optional Attributes 001 c111 010 11xx */
1378 case 0x2F:
1379// not verified
1380 ch->attr.foreground = VBI_BLACK;
1381 ch->attr.underline = c2 & 1;
1382 break;
1383
1384 default:
1385 return;
1386 }
1387
1388 /* Optional Attributes, backspace magic */
1389
1390 if (ch->col > 1 && (ch->line[ch->col - 1].unicode & 0x7F) == 0x20) {
1391 vbi_char c = ch->attr;
1392
1393 c.unicode = 0x0020;
1394 ch->line[ch->col - 1] = c;
1395 }
1396 }
1397}
1398
1399static void
1400update_display (vbi_decoder *vbi)
1401{
1402 struct caption *cc = &vbi->cc;
1403 int i;
1404
1405 for (i=0; i<8; i++)
1406 {
1407 cc_channel *ch = &cc->channel[i];
1408
1409 if (ch->update_flag == 1) {
1410 render(&ch->pg[ch->hidden ^ 1]);
1411 ch->update_flag = 0;
1412 }
1413 }
1414}
1415
1416/**
1417 * @internal
1418 * @param vbi Initialized vbi decoding context.
1419 * @param line ITU-R line number this data originated from.
1420 * @param buf Two bytes.
1421 *
1422 * Decode two bytes of Closed Caption data (Caption, XDS, ITV),
1423 * updating the decoder state accordingly. May send events.
1424 */
1425void
1426vbi_decode_caption(vbi_decoder *vbi, int line, uint8_t *buf)
1427{
1428 XDS_SEP_DEBUG("vbi_decode_caption\n");
1429 struct caption *cc = &vbi->cc;
1430 char c1 = buf[0] & 0x7F;
1431 int field2 = 1, i;
1432
1433 pthread_mutex_lock(&cc->mutex);
1434 //AM_DEBUG(1, "vbi_data: line: %d %x %x", line, buf[0], buf[1]);
1435
1436 if (line == 21) {
1437 cc->curr_chan = cc->curr_chan_f1;
1438 memcpy(cc->last, cc->last_f1, sizeof(cc->last));
1439 } else {
1440 cc->curr_chan = cc->curr_chan_f2;
1441 memcpy(cc->last, cc->last_f2, sizeof(cc->last));
1442 }
1443
1444 switch (line) {
1445 case 21: /* NTSC */
1446 case 22: /* PAL */
1447 field2 = 0;
1448 break;
1449
1450 case 335: /* PAL, hardly XDS */
1451 break;
1452
1453 case 284: /* NTSC */
1454 CC_DUMP(
1455 putchar(_vbi_to_ascii (buf[0]));
1456 putchar(_vbi_to_ascii (buf[1]));
1457 fflush(stdout);
1458 )
1459
1460 if (vbi_unpar8 (buf[0]) >= 0) { //vbi_unpar8 (buf[0]) >= 0
1461 if (c1 == 0) {
1462 goto finish;
1463 } else if (c1 <= 0x0F) {
1464 xds_separator(vbi, buf);
1465 cc->xds = (c1 != XDS_END);
1466 goto finish;
1467 } else if (c1 <= 0x1F) {
1468 xds_sub_packet *sp = cc->curr_sp;
1469 unsigned int class, type;
1470 if (sp) {
1471 class = (sp - cc->sub_packet[0]) / elements(cc->sub_packet[0]);
1472 type = (sp - cc->sub_packet[0]) % elements(cc->sub_packet[0]);
1473 xds_decoder(vbi, class, type, sp->buffer, sp->count - 2);
1474 }
1475 cc->xds = FALSE;
1476 } else if (cc->xds) {
1477 xds_separator(vbi, buf);
1478 goto finish;
1479 }
1480 } else if (cc->xds) {
1481 xds_separator(vbi, buf);
1482 goto finish;
1483 }
1484
1485 break;
1486
1487 default:
1488 goto finish;
1489 }
1490
1491 //if ( (buf[0]) < 0) { //vbi_unpar8 (buf[0]) < 0
1492 if ( vbi_unpar8 (buf[0]) < 0) { //
1493#if 0
1494 c1 = 127;
1495 buf[0] = c1; /* traditional 'bad' glyph, ccfont has */
1496 buf[1] = c1; /* room, design a special glyph? */
1497#else
1498 goto finish;
1499#endif
1500 }
1501
1502 CC_DUMP(
1503 putchar(_vbi_to_ascii (buf[0]));
1504 putchar(_vbi_to_ascii (buf[1]));
1505 fflush(stdout);
1506 )
1507
1508 switch (c1) {
1509 cc_channel *ch;
1510 vbi_char c;
1511
1512 case 0x01 ... 0x0F:
1513 /*if (!field2)*/
1514 cc->last[0] = 0;
1515 break; /* XDS field 1?? */
1516
1517 case 0x10 ... 0x1F:
1518 //if ( (buf[1]) >= 0) { // vbi_unpar8 (buf[1])
1519 if ( vbi_unpar8 (buf[1]) >= 0) {
1520 if (/*!field2
1521 &&*/ buf[0] == cc->last[0]
1522 && buf[1] == cc->last[1]) {
1523 /* cmd repetition F1: already executed */
1524 cc->last[0] = 0; /* one rep */
1525 break;
1526 }
1527
1528 caption_command(vbi, cc, c1, buf[1] & 0x7F, field2);
1529
1530 /*if (!field2)*/ {
1531 cc->last[0] = buf[0];
1532 cc->last[1] = buf[1];
1533 }
1534 } else /*if (!field2)*/
1535 cc->last[0] = 0;
1536
1537 break;
1538
1539 default:
1540 CC_TEXT_DUMP(
1541 putchar(_vbi_to_ascii (buf[0]));
1542 putchar(_vbi_to_ascii (buf[1]));
1543 fflush(stdout);
1544 )
1545#if 0
1546 AM_DEBUG(1, "text value: %c %c %x %x", _vbi_to_ascii(buf[0]),
1547 _vbi_to_ascii(buf[1]), buf[0], buf[1]);
1548#endif
1549
1550 ch = &cc->channel[(cc->curr_chan & 5) + field2 * 2];
1551
1552 if (buf[0] == 0x80 && buf[1] == 0x80) {
1553 if (ch->mode) {
1554 if (ch->nul_ct == 2)
1555 word_break(cc, ch, 1);
1556 ch->nul_ct += 2;
1557 }
1558
1559 break;
1560 }
1561
1562 /*if (!field2)*/
1563 cc->last[0] = 0;
1564
1565 ch->nul_ct = 0;
1566
1567 if (!ch->mode)
1568 break;
1569
1570 ch->time = vbi->time; /* activity measure */
1571
1572 c = ch->attr;
1573
1574 for (i = 0; i < 2; i++) {
1575 //char ci = (buf[i]) & 0x7F; /* 127 if bad */ //vbi_unpar8 (buf[i])
1576 char ci = vbi_unpar8 (buf[i]) & 0x7F; /* 127 if bad */
1577 if (ci <= 0x1F) /* 0x00 no char, 0x01 ... 0x1F invalid */
1578 continue;
1579
1580 if (ch == cc->channel + 5) // 'T2'
1581 itv_separator(vbi, cc, ci);
1582
1583 c.unicode = vbi_caption_unicode(ci, /* to_upper */ FALSE);
1584
1585 put_char(cc, ch, c);
1586 }
1587 }
1588
1589 update_display(vbi);
1590
1591 if (line == 21) {
1592 cc->curr_chan_f1 = cc->curr_chan;
1593 memcpy(cc->last_f1, cc->last, sizeof(cc->last));
1594 } else {
1595 cc->curr_chan_f2 = cc->curr_chan;
1596 memcpy(cc->last_f2, cc->last, sizeof(cc->last));
1597 }
1598
1599 finish:
1600 pthread_mutex_unlock(&cc->mutex);
1601}
1602
1603/**
1604 * @internal
1605 * @param vbi Initialized vbi decoding context.
1606 *
1607 * This function must be called after desynchronisation
1608 * has been detected (i. e. vbi data has been lost)
1609 * to reset the Closed Caption decoder.
1610 */
1611void
1612vbi_caption_desync(vbi_decoder *vbi)
1613{
1614 struct caption *cc = &vbi->cc;
1615
1616 /* cc->curr_chan = 8; *//* garbage */
1617
1618 /* cc->xds = FALSE; */
1619
1620 if (cc->curr_sp) {
1621 memset(cc->curr_sp, 0, sizeof(*(cc->curr_sp)));
1622 cc->curr_sp = NULL;
1623 }
1624
1625 cc->itv_count = 0;
1626}
1627
1628/**
1629 * @internal
1630 * @param vbi Initialized vbi decoding context.
1631 *
1632 * This function must be called after a channel switch,
1633 * to reset the Closed Caption decoder.
1634 */
1635void
1636vbi_caption_channel_switched(vbi_decoder *vbi)
1637{
1638 struct caption *cc = &vbi->cc;
1639 cc_channel *ch;
1640 int i;
1641
1642 for (i = 0; i < 9; i++) {
1643 ch = &cc->channel[i];
1644
1645 if (i < 4) {
1646 ch->mode = MODE_NONE; // MODE_ROLL_UP;
1647 ch->row = ROWS - 1;
1648 ch->row1 = ROWS - 4;
1649 ch->roll = 4;
1650 } else {
1651 ch->mode = MODE_TEXT;
1652 ch->row1 = ch->row = 0;
1653 ch->roll = ROWS;
1654 }
1655
1656 ch->attr.opacity = VBI_OPAQUE;
1657 ch->attr.foreground = VBI_WHITE;
1658 ch->attr.background = VBI_BLACK;
1659
1660 set_cursor(ch, 1, ch->row);
1661
1662 ch->time = 0.0;
1663
1664 ch->hidden = 0;
1665
1666 ch->pg[0].dirty.y0 = 0;
1667 ch->pg[0].dirty.y1 = ROWS - 1;
1668 ch->pg[0].dirty.roll = 0;
1669
1670 erase_memory(cc, ch, 0);
1671
1672 memcpy(&ch->pg[1], &ch->pg[0], sizeof(ch->pg[1]));
1673 }
1674
1675 cc->xds = FALSE;
1676
1677 memset(&cc->sub_packet, 0, sizeof(cc->sub_packet));
1678
1679 cc->info_cycle[0] = 0;
1680 cc->info_cycle[1] = 0;
1681
1682 vbi_caption_desync(vbi);
1683}
1684
1685static vbi_rgba
1686default_color_map[8] = {
1687 VBI_RGBA(0x00, 0x00, 0x00), VBI_RGBA(0xFF, 0x00, 0x00),
1688 VBI_RGBA(0x00, 0xFF, 0x00), VBI_RGBA(0xFF, 0xFF, 0x00),
1689 VBI_RGBA(0x00, 0x00, 0xFF), VBI_RGBA(0xFF, 0x00, 0xFF),
1690 VBI_RGBA(0x00, 0xFF, 0xFF), VBI_RGBA(0xFF, 0xFF, 0xFF)
1691};
1692
1693/**
1694 * @internal
1695 * @param vbi Initialized vbi decoding context.
1696 *
1697 * After the client changed text brightness and saturation
1698 * this function adjusts the Closed Caption color palette.
1699 */
1700void
1701vbi_caption_color_level(vbi_decoder *vbi)
1702{
1703 int i;
1704
1705 vbi_transp_colormap(vbi, vbi->cc.channel[0].pg[0].color_map,
1706 default_color_map, 8);
1707
1708 for (i = 1; i < 16; i++)
1709 memcpy(vbi->cc.channel[i >> 1].pg[i & 1].color_map,
1710 vbi->cc.channel[0].pg[0].color_map,
1711 sizeof(default_color_map));
1712}
1713
1714/**
1715 * @internal
1716 * @param vbi VBI decoding context.
1717 *
1718 * This function is called during @a vbi destruction
1719 * to destroy Closed Caption subset of @a vbi.
1720 */
1721void
1722vbi_caption_destroy(vbi_decoder *vbi)
1723{
1724 pthread_mutex_destroy(&vbi->cc.mutex);
1725}
1726
1727/**
1728 * @internal
1729 * @param vbi VBI decoding context.
1730 *
1731 * This function is called during @a vbi initialization
1732 * to initialize the Closed Caption subset of @a vbi.
1733 */
1734void
1735vbi_caption_init(vbi_decoder *vbi)
1736{
1737 struct caption *cc = &vbi->cc;
1738 cc_channel *ch;
1739 int i;
1740
1741 memset(cc, 0, sizeof(struct caption));
1742
1743 pthread_mutex_init(&cc->mutex, NULL);
1744
1745 for (i = 0; i < 9; i++) {
1746 ch = &cc->channel[i];
1747
1748 ch->pg[0].vbi = vbi;
1749
1750 ch->pg[0].pgno = i + 1;
1751 ch->pg[0].subno = 0;
1752
1753 ch->pg[0].rows = ROWS;
1754 ch->pg[0].columns = COLUMNS;
1755
1756 ch->pg[0].screen_color = 0;
1757 ch->pg[0].screen_opacity = (i < 4) ? VBI_TRANSPARENT_SPACE : VBI_OPAQUE;
1758
1759 ch->pg[0].font[0] = vbi_font_descriptors; /* English */
1760 ch->pg[0].font[1] = vbi_font_descriptors;
1761
1762 memcpy(&ch->pg[1], &ch->pg[0], sizeof(ch->pg[1]));
1763 }
1764
1765 for (i = 0; i < 2; i++) {
1766 cc->transp_space[i].foreground = VBI_WHITE;
1767 cc->transp_space[i].background = VBI_BLACK;
1768 cc->transp_space[i].unicode = 0x0020;
1769 }
1770
1771 cc->transp_space[0].opacity = VBI_TRANSPARENT_SPACE;
1772 cc->transp_space[1].opacity = VBI_OPAQUE;
1773
1774 vbi_caption_channel_switched(vbi);
1775
1776 vbi_caption_color_level(vbi);
1777}
1778
1779/**
1780 * @param vbi Initialized vbi decoding context.
1781 * @param pg Place to store the formatted page.
1782 * @param pgno Page number 1 ... 8 of the page to fetch, see vbi_pgno.
1783 * @param reset @c TRUE resets the vbi_page dirty fields in cache after
1784 * fetching. Pass @c FALSE only if you plan to call this function again
1785 * to update other displays.
1786 *
1787 * Fetches a Closed Caption page designated by @a pgno from the cache,
1788 * formats and stores it in @a pg. CC pages are transmitted basically in
1789 * two modes: at once and character by character ("roll-up" mode).
1790 * Either way you get a snapshot of the page as it should appear on
1791 * screen at present. With vbi_event_handler_add() you can request a
1792 * @c VBI_EVENT_CAPTION event to be notified about pending changes
1793 * (in case of "roll-up" mode that is with each new word received)
1794 * and the vbi_page->dirty fields will mark the lines actually in
1795 * need of updates, to speed up rendering.
1796 *
1797 * Although safe to do, this function is not supposed to be
1798 * called from an event handler, since rendering may block decoding
1799 * for extended periods of time.
1800 *
1801 * @return
1802 * @c FALSE if some error occured.
1803 */
1804vbi_bool
1805vbi_fetch_cc_page(vbi_decoder *vbi, vbi_page *pg, vbi_pgno pgno, vbi_bool reset)
1806{
1807 cc_channel *ch = vbi->cc.channel + ((pgno - 1) & 7);
1808 vbi_page *spg;
1809
1810 reset = reset;
1811
1812 if (pgno < 1 || pgno > 8)
1813 return FALSE;
1814
1815 pthread_mutex_lock(&vbi->cc.mutex);
1816
1817 spg = ch->pg + (ch->hidden ^ 1);
1818
1819 memcpy(pg, spg, sizeof(*pg)); /* shortcut? */
1820
1821 spg->dirty.y0 = ROWS;
1822 spg->dirty.y1 = -1;
1823 spg->dirty.roll = 0;
1824
1825 pthread_mutex_unlock(&vbi->cc.mutex);
1826
1827 return 1;
1828}
1829
1830void vbi_refresh_cc(vbi_decoder *vbi)
1831{
1832 cc_channel *ch;
1833 vbi_page *spg;
1834 int i, j;
1835 vbi_event event;
1836 struct timespec now;
1837 int flash;
1838
1839 clock_gettime(CLOCK_REALTIME, &now);
1840
1841 flash = (now.tv_nsec / 250000000) & 1;
1842
1843 pthread_mutex_lock(&vbi->cc.mutex);
1844
1845 if (flash != vbi->cc.flash_state) {
1846 vbi->cc.flash_state = flash;
1847
1848 for (i = 0; i < 8; i++)
1849 {
1850 ch = &vbi->cc.channel[i];
1851 spg = ch->pg + (ch->hidden ^ 1);
1852 for (j = 0; j < ROWS * COLUMNS; j++)
1853 {
1854 if (spg->text[j].flash)
1855 {
1856 ch->update_flag = 1;
1857 break;
1858 }
1859 }
1860 }
1861
1862 update_display(vbi);
1863 }
1864
1865 pthread_mutex_unlock(&vbi->cc.mutex);
1866}
1867
1868/*
1869Local variables:
1870c-set-style: K&R
1871c-basic-offset: 8
1872End:
1873*/
1874