summaryrefslogtreecommitdiff
path: root/gst/closedcaption/cc608_decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'gst/closedcaption/cc608_decoder.c')
-rw-r--r--gst/closedcaption/cc608_decoder.c2482
1 files changed, 2482 insertions, 0 deletions
diff --git a/gst/closedcaption/cc608_decoder.c b/gst/closedcaption/cc608_decoder.c
new file mode 100644
index 000000000..5ebec724a
--- /dev/null
+++ b/gst/closedcaption/cc608_decoder.c
@@ -0,0 +1,2482 @@
+/*
+ * libzvbi - EIA 608-B Closed Caption decoder
+ *
+ * Copyright (C) 2008 Michael H. Schimek
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+/* $Id: cc608_decoder.c,v 1.2 2009-12-14 23:43:23 mschimek Exp $ */
+
+/* This code is experimental and not yet part of the library.
+ Tests pending. */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "hamm.h"
+#include "lang.h"
+#if 0
+/* Only used for vbi_fputs_iconv_ucs2() in _vbi_cc608_dump. Switch to glib
+ * method */
+#include "conv.h"
+#endif
+#include "format.h"
+#include "event-priv.h"
+#include "cc608_decoder.h"
+
+#ifndef CC608_DECODER_LOG_INPUT
+# define CC608_DECODER_LOG_INPUT 0
+#endif
+
+enum field_num
+{
+ FIELD_1 = 0,
+ FIELD_2,
+ MAX_FIELDS
+};
+
+#define UNKNOWN_CHANNEL 0
+#define MAX_CHANNELS 8
+
+/* 47 CFR 15.119 (d) Screen format. */
+
+#define FIRST_ROW 0
+#define LAST_ROW 14
+#define MAX_ROWS 15
+
+#define ALL_ROWS_MASK ((1 << MAX_ROWS) - 1)
+
+/* Note these are visible columns. We also buffer a zeroth column
+ which is implied by 47 CFR 15.119 and EIA 608-B to set the default
+ or PAC attributes for column one, and visible as a solid space if
+ padding for legibility is enabled. We do not reserve a 33rd column
+ for padding since format_row() can just append a space to the
+ output. */
+#define FIRST_COLUMN 1
+#define LAST_COLUMN 32
+#define MAX_COLUMNS 32
+
+struct timestamp
+{
+ /* System time when the event occured, zero if no event
+ occured yet. */
+ double sys;
+
+ /* ISO 13818-1 Presentation Time Stamp of the event. Unit is
+ 1/90000 second. Only the 33 least significant bits are
+ valid. < 0 if no event occured yet. */
+ int64_t pts;
+};
+
+struct channel
+{
+ /**
+ * [0] and [1] are the displayed and non-displayed buffer as
+ * defined in 47 CFR 15.119, and selected by @a
+ * displayed_buffer below. [2] is a snapshot of the displayed
+ * buffer at the last stream event.
+ *
+ * XXX Text channels don't need buffer[2] and buffer[3], we're
+ * wasting memory.
+ */
+ uint16_t buffer[3][MAX_ROWS][1 + MAX_COLUMNS];
+
+ /**
+ * For buffer[0 ... 2], if bit 1 << row is set this row
+ * contains displayable characters, spacing or non-spacing
+ * attributes. (Special character 0x1139 "transparent space"
+ * is not a displayable character.) This information is
+ * intended to speed up copying, erasing and formatting.
+ */
+ unsigned int dirty[3];
+
+ /** Index of the displayed buffer, 0 or 1. */
+ unsigned int displayed_buffer;
+
+ /**
+ * Cursor position: FIRST_ROW ... LAST_ROW and
+ * FIRST_COLUMN ... LAST_COLUMN.
+ */
+ unsigned int curr_row;
+ unsigned int curr_column;
+
+ /**
+ * Text window height in VBI_CC608_MODE_ROLL_UP. The top row
+ * of the window is curr_row - window_rows + 1, the bottom row
+ * is curr_row.
+ *
+ * Note: curr_row - window_rows + 1 may be < FIRST_ROW, this
+ * must be clipped before using window_rows:
+ *
+ * actual_rows = MIN (curr_row - FIRST_ROW + 1, window_rows);
+ *
+ * We won't do that at the RUx command because usually a PAC
+ * command follows which may change curr_row.
+ */
+ unsigned int window_rows;
+
+ /* Most recently received PAC command. */
+ unsigned int last_pac;
+
+ /**
+ * This variable counts successive transmissions of the
+ * letters A to Z. It is reset to zero upon reception of any
+ * letter a to z.
+ *
+ * Some stations do not transmit EIA 608-B extended characters
+ * and except for N with tilde the standard and special
+ * character sets contain only lower case accented
+ * characters. We force these characters to upper case if this
+ * variable indicates live caption, where the text is usually
+ * all upper case.
+ */
+ unsigned int uppercase_predictor;
+
+ /** Current caption mode or VBI_CC608_MODE_UNKNOWN. */
+ _vbi_cc608_mode mode;
+
+ /**
+ * The time when we last received data for this
+ * channel. Intended to detect if this caption channel is
+ * active.
+ */
+ struct timestamp timestamp;
+
+ /**
+ * The time when we received the first (but not necessarily
+ * leftmost) character in the current row. Unless the mode is
+ * VBI_CC608_MODE_POP_ON the next stream event will carry this
+ * timestamp.
+ */
+ struct timestamp timestamp_c0;
+};
+
+struct _vbi_cc608_decoder
+{
+ /**
+ * Decoder state. We decode all channels in parallel, this way
+ * clients can switch between channels without data loss or
+ * capture multiple channels with a single decoder instance.
+ *
+ * Also 47 CFR 15.119 and EIA 608-C require us to remember the
+ * cursor position on each channel.
+ */
+ struct channel channel[MAX_CHANNELS];
+
+ /**
+ * Current channel, switched by caption control codes. Can be
+ * one of @c VBI_CAPTION_CC1 (1) ... @c VBI_CAPTION_CC4 (4) or
+ * @c VBI_CAPTION_T1 (5) ... @c VBI_CAPTION_T4 (8) or @c
+ * UNKNOWN_CHANNEL (0) if no channel number was received yet.
+ */
+ vbi_pgno curr_ch_num[MAX_FIELDS];
+
+ /**
+ * Caption control codes (two bytes) may repeat once for error
+ * correction. -1 if no repeated control code can be expected.
+ */
+ int expect_ctrl[MAX_FIELDS][2];
+
+ /**
+ * Receiving XDS data, as opposed to caption / ITV data.
+ * There's no XDS data on the first field, we just use an
+ * array for convenience.
+ */
+ vbi_bool in_xds[MAX_FIELDS];
+
+ /**
+ * Pointer into the channel[] array if a display update event
+ * shall be sent at the end of this iteration, @c NULL
+ * otherwise. Purpose is to suppress an event for the first of
+ * two displayable characters in a caption byte pair.
+ */
+ struct channel *event_pending;
+
+ /**
+ * Remembers past parity errors: One bit for each call of
+ * vbi_cc608_decoder_feed(), most recent result in lsb. The
+ * idea is to disable the decoder if we detect too many
+ * errors.
+ */
+ unsigned int error_history;
+
+ /**
+ * The time when we last received data, including NUL bytes.
+ * Intended to detect if the station transmits any data on
+ * line 21 or 284.
+ */
+ struct timestamp timestamp;
+
+ _vbi_event_handler_list handlers;
+};
+
+/* 47 CFR 15.119 Mid-Row Codes, Preamble Address Codes.
+ EIA 608-B Table 3. */
+static const vbi_color color_map[8] = {
+ VBI_WHITE, VBI_GREEN, VBI_BLUE, VBI_CYAN,
+ VBI_RED, VBI_YELLOW, VBI_MAGENTA,
+
+ /* Note Mid-Row Codes interpret this value as "Italics"; PACs
+ as "White Italics"; Background Attributes as "Black". */
+ VBI_BLACK
+};
+
+/* *INDENT-OFF* */
+/* 47 CFR 15.119 Preamble Address Codes. */
+static const int8_t
+pac_row_map [16] = {
+ /* 0 */ 10, /* 0x1040 */
+ /* 1 */ -1, /* no function */
+ /* 2 */ 0, 1, 2, 3, /* 0x1140 ... 0x1260 */
+ /* 6 */ 11, 12, 13, 14, /* 0x1340 ... 0x1460 */
+ /* 10 */ 4, 5, 6, 7, 8, 9 /* 0x1540 ... 0x1760 */
+};
+
+/* *INDENT-ON* */
+
+#if 0
+/* Switch to glib iconv system and gst debugging */
+/** @internal */
+void
+_vbi_cc608_dump (FILE * fp, unsigned int c1, unsigned int c2)
+{
+ const vbi_bool to_upper = FALSE;
+ const int repl_char = '?';
+ uint16_t ucs2_str[2];
+ unsigned int c;
+ unsigned int f;
+ unsigned int u;
+
+ assert (NULL != fp);
+
+ fprintf (fp, "%02X%02X %02X%c%02X%c",
+ c1 & 0xFF, c2 & 0xFF,
+ c1 & 0x7F, vbi_unpar8 (c1) < 0 ? '*' : ' ',
+ c2 & 0x7F, vbi_unpar8 (c2) < 0 ? '*' : ' ');
+
+ /* Note we ignore wrong parity. */
+ c1 &= 0x7F;
+ c2 &= 0x7F;
+
+ if (0 == c1) {
+ fputs (" null\n", fp);
+ return;
+ } else if (c1 < 0x10) {
+ if (0x0F == c1)
+ fputs (" XDS packet end\n", fp);
+ else
+ fputs (" XDS packet start/continue\n", fp);
+ return;
+ } else if (c1 >= 0x20) {
+ unsigned int i = 0;
+
+ fputs (" '", fp);
+ ucs2_str[i++] = vbi_caption_unicode (c1, to_upper);
+ if (c2 >= 0x20) {
+ ucs2_str[i++] = vbi_caption_unicode (c2, to_upper);
+ }
+ vbi_fputs_iconv_ucs2 (fp, vbi_locale_codeset (), ucs2_str, i, repl_char);
+ fprintf (fp, "'%s\n", (c2 > 0 && c2 < 0x20) ? " invalid" : "");
+ return;
+ }
+
+ /* Some common bits. */
+ c = (c1 >> 3) & 1; /* channel */
+ f = c1 & 1; /* field */
+ u = c2 & 1; /* underline */
+
+ if (c2 < 0x20) {
+ fputs (" invalid\n", fp);
+ return;
+ } else if (c2 >= 0x40) {
+ unsigned int rrrr;
+ unsigned int xxx;
+ int row;
+
+ /* Preamble Address Codes -- 001 crrr 1ri xxxu */
+
+ rrrr = (c1 & 7) * 2 + ((c2 & 0x20) > 0);
+ xxx = (c2 >> 1) & 7;
+
+ row = pac_row_map[rrrr];
+ if (c2 & 0x10) {
+ fprintf (fp, " PAC ch=%u row=%d column=%u u=%u\n", c, row, xxx * 4, u);
+ } else {
+ fprintf (fp, " PAC ch=%u row=%d color=%u u=%u\n", c, row, xxx, u);
+ }
+
+ return;
+ }
+
+ /* Control codes -- 001 caaa 01x bbbu */
+
+ switch (c1 & 0x07) {
+ case 0:
+ if (c2 < 0x30) {
+ static const char mnemo[16 * 4] =
+ "BWO\0BWS\0BGO\0BGS\0"
+ "BBO\0BBS\0BCO\0BCS\0" "BRO\0BRS\0BYO\0BYS\0" "BMO\0BMS\0BAO\0BAS";
+
+ /* Backgr. Attr. Codes -- 001 c000 010 xxxt */
+
+ fprintf (fp, " %s ch=%u\n", mnemo + (c2 & 0xF) * 4, c);
+ return;
+ }
+ break;
+
+ case 1:
+ if (c2 < 0x30) {
+ unsigned int xxx;
+
+ /* Mid-Row Codes -- 001 c001 010 xxxu */
+
+ xxx = (c2 >> 1) & 7;
+ fprintf (fp, " mid-row ch=%u color=%u u=%u\n", c, xxx, u);
+ } else {
+ /* Special Characters -- 001 c001 011 xxxx */
+
+ fprintf (fp, " special character ch=%u '", c);
+ ucs2_str[0] = vbi_caption_unicode (0x1100 | c2, to_upper);
+ vbi_fputs_iconv_ucs2 (fp, vbi_locale_codeset (),
+ ucs2_str, 1, repl_char);
+ fputs ("'\n", fp);
+ }
+ return;
+
+ case 2: /* first group */
+ case 3: /* second group */
+ /* Extended Character Set -- 001 c01x 01x xxxx */
+
+ fprintf (fp, " extended character ch=%u '", c);
+ ucs2_str[0] = vbi_caption_unicode (c1 * 256 + c2, to_upper);
+ vbi_fputs_iconv_ucs2 (fp, vbi_locale_codeset (), ucs2_str, 1, repl_char);
+ fputs ("'\n", fp);
+ return;
+
+ case 4: /* f = 0 */
+ case 5: /* f = 1 */
+ if (c2 < 0x30) {
+ static const char mnemo[16 * 4] =
+ "RCL\0BS \0AOF\0AON\0"
+ "DER\0RU2\0RU3\0RU4\0" "FON\0RDC\0TR \0RTD\0" "EDM\0CR \0ENM\0EOC";
+
+ /* Misc. Control Codes -- 001 c10f 010 xxxx */
+
+ fprintf (fp, " %s ch=%u f=%u\n", mnemo + (c2 & 0xF) * 4, c, f);
+ return;
+ }
+ break;
+
+ case 6: /* reserved */
+ break;
+
+ case 7:
+ switch (c2) {
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ fprintf (fp, " TO%u ch=%u\n", c2 - 0x20, c);
+ return;
+
+ case 0x2D:
+ fprintf (fp, " BT ch=%u\n", c);
+ return;
+
+ case 0x2E:
+ fprintf (fp, " FA ch=%u\n", c);
+ return;
+
+ case 0x2F:
+ fprintf (fp, " FAU ch=%u\n", c);
+ return;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ fprintf (fp, " unknown\n");
+}
+#endif
+
+/* Future stuff. */
+enum
+{
+ VBI_UNDERLINE = (1 << 0),
+ VBI_ITALIC = (1 << 2),
+ VBI_FLASH = (1 << 3)
+};
+
+#if 0 /* UNUSED */
+_vbi_inline void
+vbi_char_copy_attr (struct vbi_char *cp1,
+ const struct vbi_char *cp2, unsigned int attr)
+{
+ if (attr & VBI_UNDERLINE)
+ cp1->underline = cp2->underline;
+ if (attr & VBI_ITALIC)
+ cp1->italic = cp2->italic;
+ if (attr & VBI_FLASH)
+ cp1->flash = cp2->flash;
+}
+#endif /* UNUSED */
+
+_vbi_inline void
+vbi_char_clear_attr (struct vbi_char *cp, unsigned int attr)
+{
+ if (attr & VBI_UNDERLINE)
+ cp->underline = 0;
+ if (attr & VBI_ITALIC)
+ cp->italic = 0;
+ if (attr & VBI_FLASH)
+ cp->flash = 0;
+}
+
+_vbi_inline void
+vbi_char_set_attr (struct vbi_char *cp, unsigned int attr)
+{
+ if (attr & VBI_UNDERLINE)
+ cp->underline = 1;
+ if (attr & VBI_ITALIC)
+ cp->italic = 1;
+ if (attr & VBI_FLASH)
+ cp->flash = 1;
+}
+
+#if 0 /* UNUSEd */
+_vbi_inline unsigned int
+vbi_char_has_attr (const struct vbi_char *cp, unsigned int attr)
+{
+ attr &= (VBI_UNDERLINE | VBI_ITALIC | VBI_FLASH);
+
+ if (0 == cp->underline)
+ attr &= ~VBI_UNDERLINE;
+ if (0 == cp->italic)
+ attr &= ~VBI_ITALIC;
+ if (0 == cp->flash)
+ attr &= ~VBI_FLASH;
+
+ return attr;
+}
+
+_vbi_inline unsigned int
+vbi_char_xor_attr (const struct vbi_char *cp1,
+ const struct vbi_char *cp2, unsigned int attr)
+{
+ attr &= (VBI_UNDERLINE | VBI_ITALIC | VBI_FLASH);
+
+ if (0 == (cp1->underline ^ cp2->underline))
+ attr &= ~VBI_UNDERLINE;
+ if (0 == (cp1->italic ^ cp2->italic))
+ attr &= ~VBI_ITALIC;
+ if (0 == (cp1->flash ^ cp2->flash))
+ attr &= ~VBI_FLASH;
+
+ return attr;
+}
+#endif /* UNUSED */
+
+static void
+timestamp_reset (struct timestamp *ts)
+{
+ ts->sys = 0.0;
+ ts->pts = -1;
+}
+
+static vbi_bool
+timestamp_is_set (const struct timestamp *ts)
+{
+ return (ts->pts >= 0 || ts->sys > 0.0);
+}
+
+static vbi_pgno
+channel_num (const _vbi_cc608_decoder * cd, const struct channel *ch)
+{
+ return (ch - cd->channel) + 1;
+}
+
+/* This implementation of character attributes is based on 47 CFR
+ 15.119 (h) and the following sections of EIA 608-B:
+
+ EIA 608-B Annex C.7 "Preamble Address Codes and Tab Offsets
+ (Regulatory/Preferred)": "In general, Preamble Address Codes (PACs)
+ have no immediate effect on the display. A major exception is the
+ receipt of a PAC during roll-up captioning. In that case, if the
+ base row designated in the PAC is not the same as the current base
+ row, the display shall be moved immediately to the new base
+ row. [...] An indenting PAC carries the attributes of white,
+ non-italicized, and it sets underlining on or off. Tab Offset
+ commands do not change these attributes. If an indenting PAC with
+ underline ON is received followed by a Tab Offset and by text, the
+ text shall be underlined (except as noted below). When a
+ displayable character is received, it is deposited at the current
+ cursor position. If there is already a displayable character in the
+ column immediately to the left, the new character assumes the
+ attributes of that character. The new character may be arriving as
+ the result of an indenting PAC (with or without a Tab Offset), and
+ that PAC may designate other attributes, but the new character is
+ forced to assume the attributes of the character immediately to its
+ left, and the PAC's attributes are ignored. If, when a displayable
+ character is received, it overwrites an existing PAC or mid-row
+ code, and there are already characters to the right of the new
+ character, these existing characters shall assume the same
+ attributes as the new character. This adoption can result in a
+ whole caption row suddenly changing color, underline, italics,
+ and/or flash attributes."
+
+ EIA 608-B Annex C.14 "Special Cases Regarding Attributes
+ (Normative)": "In most cases, Preamble Address Codes shall set
+ attributes for the caption elements they address. It is
+ theoretically possible for a service provider to use an indenting
+ PAC to start a row at Column 5 or greater, and then to use
+ Backspace to move the cursor to the left of the PAC into an area to
+ which no attributes have been assigned. It is also possible for a
+ roll-up row, having been created by a Carriage Return, to receive
+ characters with no PAC used to set attributes. In these cases, and
+ in any other case where no explicit attributes have been assigned,
+ the display shall be white, non-underlined, non-italicized, and
+ non-flashing. In case new displayable characters are received
+ immediately after a Delete to End of Row (DER), the display
+ attributes of the first deleted character shall remain in effect if
+ there is a displayable character to the left of the cursor;
+ otherwise, the most recently received PAC shall set the display
+ attributes."
+
+ 47 CFR 15.119 (n) clarifies that Special Character "transparent
+ space" is not a "displayable character". */
+
+/**
+ * @internal
+ * @param to_upper Convert the lower case Latin characters in the
+ * standard character set to upper case.
+ * @param padding Add spaces around words for improved legibility
+ * as defined in 47 CFR 15.119. If @c TRUE the resulting page will
+ * be 34 columns wide, otherwise 32 columns. The height is always 15
+ * rows.
+ * @param alpha Add an offset to the vbi_color of characters: +0 for
+ * opaque, +8 for translucent, +16 for transparent characters. Intended
+ * for formatting with an alpha color map.
+ */
+static void
+format_row (struct vbi_char *cp,
+ unsigned int max_columns,
+ const struct channel *ch,
+ unsigned int buffer,
+ unsigned int row, vbi_bool to_upper, vbi_bool padding, vbi_bool alpha)
+{
+ struct vbi_char ac;
+ struct vbi_char ac_ts;
+ vbi_char *end;
+ unsigned int i;
+
+ /* 47 CFR 15.119 (h)(1). EIA 608-B Section 6.4. */
+ CLEAR (ac);
+ ac.opacity = VBI_OPAQUE;
+ ac.foreground = VBI_WHITE;
+ ac.background = VBI_BLACK;
+
+ ac_ts = ac;
+ ac_ts.unicode = 0x20;
+ ac_ts.opacity = VBI_TRANSPARENT_SPACE;
+ if (alpha) {
+ ac_ts.foreground += 16;
+ ac_ts.background += 16;
+ }
+
+ end = cp + MAX_COLUMNS;
+ if (padding)
+ end += 2;
+
+ assert (end <= cp + max_columns);
+
+ /* Shortcut. */
+ if (0 == (ch->dirty[buffer] & (1 << row))) {
+ while (cp < end)
+ *cp++ = ac_ts;
+
+ return;
+ }
+
+ if (padding) {
+ *cp++ = ac_ts;
+ }
+
+ for (i = FIRST_COLUMN - 1; i <= LAST_COLUMN; ++i) {
+ unsigned int c;
+
+ ac.unicode = 0x20;
+
+ c = ch->buffer[buffer][row][i];
+ if (0 == c) {
+ if (padding
+ && VBI_TRANSPARENT_SPACE != cp[-1].opacity
+ && 0x20 != cp[-1].unicode) {
+ /* Append a space with the same colors
+ and opacity (opaque or transp.
+ backgr.) as the text to the left of
+ it. */
+ *cp++ = ac;
+ /* We don't underline spaces, see
+ below. */
+ vbi_char_clear_attr (cp - 1, -1);
+ } else if (i > 0) {
+ *cp++ = ac;
+ cp[-1].opacity = VBI_TRANSPARENT_SPACE;
+ if (alpha) {
+ cp[-1].foreground = 16 + (ac.foreground & 7);
+ cp[-1].background = 16 + (ac.background & 7);
+ }
+ }
+
+ continue;
+ } else if (c < 0x1020) {
+ if (padding && VBI_TRANSPARENT_SPACE == cp[-1].opacity) {
+ /* Prepend a space with the same
+ colors and opacity (opaque or
+ transp. backgr.) as the text to the
+ right of it. */
+ cp[-1] = ac;
+ /* We don't underline spaces, see
+ below. */
+ vbi_char_clear_attr (cp - 1, -1);
+ }
+
+ if ((c >= 'a' && c <= 'z')
+ || 0x7E == c /* n with tilde */ ) {
+ /* We do not force these characters to
+ upper case because the standard
+ character set includes upper case
+ versions of these characters and
+ lower case was probably
+ deliberately transmitted. */
+ ac.unicode = vbi_caption_unicode (c, /* to_upper */ FALSE);
+ } else {
+ ac.unicode = vbi_caption_unicode (c, to_upper);
+ }
+ } else if (c < 0x1040) {
+ unsigned int color;
+
+ /* Backgr. Attr. Codes -- 001 c000 010 xxxt */
+ /* EIA 608-B Section 6.2. */
+
+ /* This is a set-at spacing attribute. */
+
+ color = (c >> 1) & 7;
+ ac.background = color_map[color];
+
+ if (c & 0x0001) {
+ if (alpha)
+ ac.background += 8;
+ ac.opacity = VBI_SEMI_TRANSPARENT;
+ } else {
+ ac.opacity = VBI_OPAQUE;
+ }
+ } else if (c < 0x1120) {
+ /* Preamble Address Codes -- 001 crrr 1ri xxxu */
+
+ /* PAC is a non-spacing attribute and only
+ stored in the buffer at the addressed
+ column minus one if it replaces a
+ transparent space (EIA 608-B Annex C.7,
+ C.14). There's always a transparent space
+ to the left of the first column but we show
+ this zeroth column only if padding is
+ enabled. */
+ if (padding
+ && VBI_TRANSPARENT_SPACE != cp[-1].opacity
+ && 0x20 != cp[-1].unicode) {
+ /* See 0 == c. */
+ *cp++ = ac;
+ vbi_char_clear_attr (cp - 1, -1);
+ } else if (i > 0) {
+ *cp++ = ac;
+ cp[-1].opacity = VBI_TRANSPARENT_SPACE;
+ if (alpha) {
+ cp[-1].foreground = 16 + (ac.foreground & 7);
+ cp[-1].background = 16 + (ac.background & 7);
+ }
+ }
+
+ vbi_char_clear_attr (&ac, VBI_UNDERLINE | VBI_ITALIC);
+ if (c & 0x0001)
+ vbi_char_set_attr (&ac, VBI_UNDERLINE);
+ if (c & 0x0010) {
+ ac.foreground = VBI_WHITE;
+ } else {
+ unsigned int color;
+
+ color = (c >> 1) & 7;
+ if (7 == color) {
+ ac.foreground = VBI_WHITE;
+ vbi_char_set_attr (&ac, VBI_ITALIC);
+ } else {
+ ac.foreground = color_map[color];
+ }
+ }
+
+ continue;
+ } else if (c < 0x1130) {
+ unsigned int color;
+
+ /* Mid-Row Codes -- 001 c001 010 xxxu */
+ /* 47 CFR 15.119 Mid-Row Codes table,
+ (h)(1)(ii), (h)(1)(iii). */
+
+ /* 47 CFR 15.119 (h)(1)(i), EIA 608-B Section
+ 6.2: Mid-Row codes, FON, BT, FA and FAU are
+ set-at spacing attributes. */
+
+ vbi_char_clear_attr (&ac, -1);
+ if (c & 0x0001)
+ vbi_char_set_attr (&ac, VBI_UNDERLINE);
+ color = (c >> 1) & 7;
+ if (7 == color) {
+ vbi_char_set_attr (&ac, VBI_ITALIC);
+ } else {
+ ac.foreground = color_map[color];
+ }
+ } else if (c < 0x1220) {
+ /* Special Characters -- 001 c001 011 xxxx */
+ /* 47 CFR 15.119 Character Set Table. */
+
+ if (padding && VBI_TRANSPARENT_SPACE == cp[-1].opacity) {
+ cp[-1] = ac;
+ vbi_char_clear_attr (cp - 1, -1);
+ }
+
+ assert (0x1139 /* transparent space */ != c);
+ ac.unicode = vbi_caption_unicode (c, to_upper);
+ } else if (c < 0x1428) {
+ /* Extended Character Set -- 001 c01x 01x xxxx */
+ /* EIA 608-B Section 6.4.2 */
+
+ if (padding && VBI_TRANSPARENT_SPACE == cp[-1].opacity) {
+ cp[-1] = ac;
+ vbi_char_clear_attr (cp - 1, -1);
+ }
+
+ /* We do not force these characters to upper
+ case because the extended character set
+ includes upper case versions of all letters
+ and lower case was probably deliberately
+ transmitted. */
+ ac.unicode = vbi_caption_unicode (c, /* to_upper */ FALSE);
+
+ if (0x2500 == (ac.unicode & 0xFFE0)) {
+ /* Box drawing characters probably
+ shouldn't have these attributes. */
+ *cp++ = ac;
+ vbi_char_clear_attr (cp - 1, (VBI_ITALIC | VBI_UNDERLINE));
+ continue;
+ }
+ } else if (c < 0x172D) {
+ /* FON Flash On -- 001 c10f 010 1000 */
+ /* 47 CFR 15.119 (h)(1)(iii). */
+
+ vbi_char_set_attr (&ac, VBI_FLASH);
+ } else if (c < 0x172E) {
+ /* BT Background Transparent -- 001 c111 010 1101 */
+ /* EIA 608-B Section 6.4. */
+
+ ac.opacity = VBI_TRANSPARENT_FULL;
+ if (alpha) {
+ ac.background = 16 + (ac.background & 7);
+ }
+ } else if (c <= 0x172F) {
+ /* FA Foreground Black -- 001 c111 010 111u */
+ /* EIA 608-B Section 6.4. */
+
+ vbi_char_clear_attr (&ac, -1);
+ if (c & 0x0001)
+ vbi_char_set_attr (&ac, VBI_UNDERLINE);
+ ac.foreground = VBI_BLACK;
+ }
+
+ *cp++ = ac;
+
+ /* 47 CFR 15.119 and EIA 608-B are silent about
+ underlined spaces, but considering the example in
+ 47 CFR (h)(1)(iv) which would produce something
+ ugly like "__text" I suppose we should not
+ underline them. For good measure we also clear the
+ invisible italic and flash attribute. */
+ if (0x20 == ac.unicode)
+ vbi_char_clear_attr (cp - 1, -1);
+ }
+
+ if (padding) {
+ ac.unicode = 0x20;
+ vbi_char_clear_attr (&ac, -1);
+
+ if (VBI_TRANSPARENT_SPACE != cp[-1].opacity && 0x20 != cp[-1].unicode) {
+ *cp++ = ac;
+ } else {
+ ac.opacity = VBI_TRANSPARENT_SPACE;
+ ac.foreground = 16 + (ac.foreground & 7);
+ ac.background = 16 + (ac.background & 7);
+ *cp++ = ac;
+ }
+ }
+
+ assert (cp == end);
+}
+
+#ifndef VBI_RGBA
+# define VBI_RGBA(r, g, b) \
+ ((((r) & 0xFF) << 0) | (((g) & 0xFF) << 8) \
+ | (((b) & 0xFF) << 16) | (0xFF << 24))
+#endif
+
+/**
+ * @param cd Caption decoder allocated with vbi_cc608_decoder_new().
+ * @param pg The display state will be stored here.
+ * @param channel Caption channel @c VBI_CHANNEL_CC1 ...
+ * @c VBI_CHANNEL_CC4 or @c VBI_CHANNEL_T1 ... @c VBI_CHANNEL_T4.
+ * @param padding Add spaces around words for improved legibility
+ * as defined in 47 CFR 15.119. If @c TRUE the resulting page will
+ * be 34 columns wide, otherwise 32 columns. The height is always 15
+ * rows.
+ *
+ * This function stores the current display state of the given caption
+ * channel in the @a pg structure. (There is no channel switch
+ * function; all channels are decoded simultaneously.)
+ *
+ * All fields of @a pg will be initialized but the @a vbi, @a nuid, @a
+ * dirty, @a nav_link, @a nav_index, and @a font fields will not
+ * contain useful information.
+ *
+ * @returns
+ * @c FALSE on failure: The channel number is out of bounds.
+ */
+vbi_bool
+_vbi_cc608_decoder_get_page (_vbi_cc608_decoder * cd,
+ vbi_page * pg, vbi_pgno channel, vbi_bool padding)
+{
+ static const vbi_rgba default_color_map[3 * 8] = {
+ 0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF,
+ 0xFFFF0000, 0xFFFF00FF, 0xFFFFFF00, 0xFFFFFFFF,
+
+ 0x80000000, 0x800000FF, 0x8000FF00, 0x8000FFFF,
+ 0x80FF0000, 0x80FF00FF, 0x80FFFF00, 0x80FFFFFF,
+
+ 0x00000000, 0x000000FF, 0x0000FF00, 0x0000FFFF,
+ 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00FFFFFF,
+ };
+ vbi_char *cp;
+ struct channel *ch;
+ unsigned int row;
+ vbi_bool to_upper;
+
+ assert (NULL != cd);
+ assert (NULL != pg);
+
+ if (channel < VBI_CAPTION_CC1 || channel > VBI_CAPTION_T4)
+ return FALSE;
+
+ ch = &cd->channel[channel - VBI_CAPTION_CC1];
+
+ CLEAR (*pg);
+
+ pg->pgno = channel;
+
+ pg->rows = MAX_ROWS;
+
+ if (padding)
+ pg->columns = MAX_COLUMNS + 2;
+ else
+ pg->columns = MAX_COLUMNS;
+
+ assert (N_ELEMENTS (pg->text) >= MAX_ROWS * (MAX_COLUMNS + 2));
+
+ pg->dirty.y1 = LAST_ROW;
+
+ pg->screen_opacity = VBI_TRANSPARENT_SPACE;
+
+ assert (sizeof (pg->color_map) >= sizeof (default_color_map));
+ memcpy (pg->color_map, default_color_map, sizeof (default_color_map));
+
+ cp = pg->text;
+
+ to_upper = (ch->uppercase_predictor > 3);
+
+ for (row = 0; row < MAX_ROWS; ++row) {
+ format_row (cp, pg->columns,
+ ch, ch->displayed_buffer, row, to_upper, padding,
+ /* alpha */ TRUE);
+
+ cp += pg->columns;
+ }
+
+ return TRUE;
+}
+
+static void
+display_event (_vbi_cc608_decoder * cd,
+ struct channel *ch, _vbi_cc608_event_flags flags)
+{
+ vbi_event ev;
+ struct _vbi_event_cc608_page cc608;
+
+ CLEAR (ev);
+
+ ev.type = _VBI_EVENT_CC608;
+ ev.ev._cc608 = &cc608;
+ cc608.channel = channel_num (cd, ch);
+ cc608.mode = ch->mode;
+ cc608.flags = flags;
+
+ _vbi_event_handler_list_send (&cd->handlers, &ev);
+}
+
+/* This decoder is mainly designed to overlay caption onto live video,
+ but to create transcripts we also offer an event every time a line
+ of caption is complete. The event occurs when certain control codes
+ are received:
+
+ In POP_ON mode we send the event upon reception of EOC, which swaps
+ the displayed and non-displayed memory.
+
+ In ROLL_UP and TEXT mode captioners are not expected to display new
+ text by erasing and overwriting a row with PAC, TOx, BS and DER so
+ we do not send an event on reception of these codes. In ROLL_UP
+ mode CR, EDM, EOC, RCL and RDC complete a line: CR moves the cursor
+ to a new row, EDM erases the displayed memory. The remaining codes
+ switch to POP_ON or PAINT_ON mode. In TEXT mode CR and TR are our
+ line completion indicators. CR works as above and TR erases the
+ displayed memory. EDM, EOC, RDC, RCL and RUx have no effect on Text
+ channels according to EIA 608.
+
+ In PAINT_ON mode RDC never erases the displayed memory and CR has
+ no function. Instead captioners can freely position the cursor and
+ erase or overwrite (parts of) rows with PAC, TOx, BS and DER, or
+ erase all rows with EDM. We send an event on PAC, EDM, EOC, RCL and
+ RUx, provided the characters (including spacing attributes) in the
+ current row changed since the last event: PAC is the only control
+ code which can move the cursor to the left and/or to a new row, and
+ likely to introduce a new line. EOC, RCL and RUx switch to POP_ON
+ or ROLL_UP mode. */
+
+static void
+stream_event (_vbi_cc608_decoder * cd,
+ struct channel *ch, unsigned int first_row, unsigned int last_row)
+{
+ vbi_event ev;
+ struct _vbi_event_cc608_stream cc608_stream;
+ unsigned int row;
+ vbi_bool to_upper;
+
+ CLEAR (ev);
+
+ ev.type = _VBI_EVENT_CC608_STREAM;
+ ev.ev._cc608_stream = &cc608_stream;
+ cc608_stream.capture_time = ch->timestamp_c0.sys;
+ cc608_stream.pts = ch->timestamp_c0.pts;
+ cc608_stream.channel = channel_num (cd, ch);
+ cc608_stream.mode = ch->mode;
+
+ to_upper = (ch->uppercase_predictor > 3);
+
+ for (row = first_row; row <= last_row; ++row) {
+ unsigned int end;
+
+ format_row (cc608_stream.text,
+ N_ELEMENTS (cc608_stream.text), ch, ch->displayed_buffer, row, to_upper,
+ /* padding */ FALSE,
+ /* alpha */ FALSE);
+
+ for (end = N_ELEMENTS (cc608_stream.text); end > 0; --end) {
+ if (VBI_TRANSPARENT_SPACE != cc608_stream.text[end - 1].opacity)
+ break;
+ }
+
+ if (0 == end)
+ continue;
+
+ _vbi_event_handler_list_send (&cd->handlers, &ev);
+ }
+
+ timestamp_reset (&ch->timestamp_c0);
+}
+
+static void
+put_char (_vbi_cc608_decoder * cd,
+ struct channel *ch, int c, vbi_bool displayable, vbi_bool backspace)
+{
+ uint16_t *text;
+ unsigned int curr_buffer;
+ unsigned int row;
+ unsigned int column;
+
+ /* 47 CFR Section 15.119 (f)(1), (f)(2), (f)(3). */
+ curr_buffer = ch->displayed_buffer ^ (_VBI_CC608_MODE_POP_ON == ch->mode);
+
+ row = ch->curr_row;
+ column = ch->curr_column;
+
+ if (unlikely (backspace)) {
+ /* 47 CFR 15.119 (f)(1)(vi), (f)(2)(ii),
+ (f)(3)(i). EIA 608-B Section 6.4.2, 7.4. */
+ if (column > FIRST_COLUMN)
+ --column;
+ } else {
+ /* 47 CFR 15.119 (f)(1)(v), (f)(1)(vi), (f)(2)(ii),
+ (f)(3)(i). EIA 608-B Section 7.4. */
+ if (column < LAST_COLUMN)
+ ch->curr_column = column + 1;
+ }
+
+ text = &ch->buffer[curr_buffer][row][0];
+ text[column] = c;
+
+ /* Send a display update event when the displayed buffer of
+ the current channel changed, but no more than once for each
+ pair of Closed Caption bytes. */
+ /* XXX This may not be a visible change, but such cases are
+ rare and we'd probably need a function almost as complex as
+ format_row() to find out. */
+ if (_VBI_CC608_MODE_POP_ON != ch->mode) {
+ cd->event_pending = ch;
+ }
+
+ if (likely (displayable)) {
+ /* EIA 608-B Annex C.7, C.14. */
+ if (FIRST_COLUMN == column || 0 == text[column - 1]) {
+ /* Note last_pac may be 0 as well. */
+ text[column - 1] = ch->last_pac;
+ }
+
+ if (c >= 'a' && c <= 'z') {
+ ch->uppercase_predictor = 0;
+ } else if (c >= 'A' && c <= 'Z') {
+ unsigned int up;
+
+ up = ch->uppercase_predictor + 1;
+ if (up > 0)
+ ch->uppercase_predictor = up;
+ }
+ } else if (unlikely (0 == c)) {
+ unsigned int i;
+
+ /* This is special character "transparent space". */
+
+ for (i = FIRST_COLUMN; i <= LAST_COLUMN; ++i)
+ c |= ch->buffer[curr_buffer][row][i];
+
+ ch->dirty[curr_buffer] &= ~((0 == c) << row);
+
+ return;
+ } else {
+ /* This is a spacing attribute. */
+
+ /* EIA 608-B Annex C.7, C.14. */
+ if (FIRST_COLUMN == column || 0 == text[column - 1]) {
+ /* Note last_pac may be 0 as well. */
+ text[column - 1] = ch->last_pac;
+ }
+ }
+
+ assert (sizeof (ch->dirty[0]) * 8 - 1 >= MAX_ROWS);
+ ch->dirty[curr_buffer] |= 1 << row;
+
+ if (!timestamp_is_set (&ch->timestamp_c0)) {
+ ch->timestamp_c0 = cd->timestamp;
+ }
+}
+
+static void
+ext_control_code (_vbi_cc608_decoder * cd, struct channel *ch, unsigned int c2)
+{
+ unsigned int column;
+
+ switch (c2) {
+ case 0x21: /* TO1 */
+ case 0x22: /* TO2 */
+ case 0x23: /* TO3 Tab Offset -- 001 c111 010 00xx */
+ /* 47 CFR 15.119 (e)(1)(ii). EIA 608-B Section 7.4,
+ Annex C.7. */
+ column = ch->curr_column + (c2 & 3);
+ ch->curr_column = MIN (column, (unsigned int) LAST_COLUMN);
+ break;
+
+ case 0x24: /* Select standard character set in normal size */
+ case 0x25: /* Select standard character set in double size */
+ case 0x26: /* Select first private character set */
+ case 0x27: /* Select second private character set */
+ case 0x28: /* Select character set GB 2312-80 (Chinese) */
+ case 0x29: /* Select character set KSC 5601-1987 (Korean) */
+ case 0x2A: /* Select first registered character set. */
+ /* EIA 608-B Section 6.3 Closed Group Extensions. */
+ break;
+
+ case 0x2D: /* BT Background Transparent -- 001 c111 010 1101 */
+ case 0x2E: /* FA Foreground Black -- 001 c111 010 1110 */
+ case 0x2F: /* FAU Foregr. Black Underl. -- 001 c111 010 1111 */
+ /* EIA 608-B Section 6.2. */
+ put_char (cd, ch, 0x1700 | c2,
+ /* displayable */ FALSE,
+ /* backspace */ TRUE);
+ break;
+
+ default:
+ /* 47 CFR Section 15.119 (j): Ignore. */
+ break;
+ }
+}
+
+/* Send a stream event if the current row has changed since the last
+ stream event. This is necessary in paint-on mode where CR has no
+ function and captioners can freely position the cursor to erase or
+ overwrite (parts of) rows. */
+static void
+stream_event_if_changed (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int curr_buffer;
+ unsigned int row;
+ unsigned int i;
+
+ curr_buffer = ch->displayed_buffer;
+ row = ch->curr_row;
+
+ if (0 == (ch->dirty[curr_buffer] & (1 << row)))
+ return;
+
+ for (i = FIRST_COLUMN; i <= LAST_COLUMN; ++i) {
+ unsigned int c1;
+ unsigned int c2;
+
+ c1 = ch->buffer[curr_buffer][row][i];
+ if (c1 >= 0x1040) {
+ if (c1 < 0x1120) {
+ c1 = 0; /* PAC -- non-spacing */
+ } else if (c1 < 0x1130 || c1 >= 0x1428) {
+ /* MR, FON, BT, FA, FAU -- spacing */
+ c1 = 0x20;
+ }
+ }
+
+ c2 = ch->buffer[2][row][i];
+ if (c2 >= 0x1040) {
+ if (c2 < 0x1120) {
+ c2 = 0;
+ } else if (c2 < 0x1130 || c2 >= 0x1428) {
+ c1 = 0x20;
+ }
+ }
+
+ if (c1 != c2) {
+ stream_event (cd, ch, row, row);
+
+ memcpy (ch->buffer[2][row],
+ ch->buffer[curr_buffer][row], sizeof (ch->buffer[0][0]));
+
+ ch->dirty[2] = ch->dirty[curr_buffer];
+
+ return;
+ }
+ }
+}
+
+static void
+end_of_caption (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int curr_buffer;
+ unsigned int row;
+
+ /* EOC End Of Caption -- 001 c10f 010 1111 */
+
+ curr_buffer = ch->displayed_buffer;
+
+ switch (ch->mode) {
+ case _VBI_CC608_MODE_UNKNOWN:
+ case _VBI_CC608_MODE_POP_ON:
+ break;
+
+ case _VBI_CC608_MODE_ROLL_UP:
+ row = ch->curr_row;
+ if (0 != (ch->dirty[curr_buffer] & (1 << row)))
+ stream_event (cd, ch, row, row);
+ break;
+
+ case _VBI_CC608_MODE_PAINT_ON:
+ stream_event_if_changed (cd, ch);
+ break;
+
+ case _VBI_CC608_MODE_TEXT:
+ /* Not reached. (ch is a caption channel.) */
+ return;
+ }
+
+ ch->displayed_buffer = curr_buffer ^= 1;
+
+ /* 47 CFR Section 15.119 (f)(2). */
+ ch->mode = _VBI_CC608_MODE_POP_ON;
+
+ if (0 != ch->dirty[curr_buffer]) {
+ ch->timestamp_c0 = cd->timestamp;
+
+ stream_event (cd, ch, FIRST_ROW, LAST_ROW);
+
+ display_event (cd, ch, /* flags */ 0);
+ }
+}
+
+static void
+carriage_return (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int curr_buffer;
+ unsigned int row;
+ unsigned int window_rows;
+ unsigned int first_row;
+
+ /* CR Carriage Return -- 001 c10f 010 1101 */
+
+ curr_buffer = ch->displayed_buffer;
+ row = ch->curr_row;
+
+ switch (ch->mode) {
+ case _VBI_CC608_MODE_UNKNOWN:
+ return;
+
+ case _VBI_CC608_MODE_ROLL_UP:
+ /* 47 CFR Section 15.119 (f)(1)(iii). */
+ ch->curr_column = FIRST_COLUMN;
+
+ /* 47 CFR 15.119 (f)(1): "The cursor always remains on
+ the base row." */
+
+ /* XXX Spec? */
+ ch->last_pac = 0;
+
+ /* No event if the buffer contains only
+ TRANSPARENT_SPACEs. */
+ if (0 == ch->dirty[curr_buffer])
+ return;
+
+ window_rows = MIN (row + 1 - FIRST_ROW, ch->window_rows);
+ break;
+
+ case _VBI_CC608_MODE_POP_ON:
+ case _VBI_CC608_MODE_PAINT_ON:
+ /* 47 CFR 15.119 (f)(2)(i), (f)(3)(i): No effect. */
+ return;
+
+ case _VBI_CC608_MODE_TEXT:
+ /* 47 CFR Section 15.119 (f)(1)(iii). */
+ ch->curr_column = FIRST_COLUMN;
+
+ /* XXX Spec? */
+ ch->last_pac = 0;
+
+ /* EIA 608-B Section 7.4: "When Text Mode has
+ initially been selected and the specified Text
+ memory is empty, the cursor starts at the topmost
+ row, Column 1, and moves down to Column 1 on the
+ next row each time a Carriage Return is received
+ until the last available row is reached. A variety
+ of methods may be used to accomplish the scrolling,
+ provided that the text is legible while moving. For
+ example, as soon as all of the available rows of
+ text are on the screen, Text Mode switches to the
+ standard roll-up type of presentation." */
+
+ if (LAST_ROW != row) {
+ if (0 != (ch->dirty[curr_buffer] & (1 << row))) {
+ stream_event (cd, ch, row, row);
+ }
+
+ ch->curr_row = row + 1;
+
+ return;
+ }
+
+ /* No event if the buffer contains all
+ TRANSPARENT_SPACEs. */
+ if (0 == ch->dirty[curr_buffer])
+ return;
+
+ window_rows = MAX_ROWS;
+
+ break;
+ }
+
+ /* 47 CFR Section 15.119 (f)(1)(iii). */
+
+ if (0 != (ch->dirty[curr_buffer] & (1 << row))) {
+ stream_event (cd, ch, row, row);
+ }
+
+ first_row = row + 1 - window_rows;
+ memmove (ch->buffer[curr_buffer][first_row],
+ ch->buffer[curr_buffer][first_row + 1],
+ (window_rows - 1) * sizeof (ch->buffer[0][0]));
+
+ ch->dirty[curr_buffer] >>= 1;
+
+ memset (ch->buffer[curr_buffer][row], 0, sizeof (ch->buffer[0][0]));
+
+ /* See the description of VBI_CC608_START_ROLLING and
+ test/caption for the expected effect. */
+ display_event (cd, ch, _VBI_CC608_START_ROLLING);
+}
+
+static void
+erase_memory (_vbi_cc608_decoder * cd, struct channel *ch, unsigned int buffer)
+{
+ if (0 != ch->dirty[buffer]) {
+ CLEAR (ch->buffer[buffer]);
+
+ ch->dirty[buffer] = 0;
+
+ if (buffer == ch->displayed_buffer)
+ display_event (cd, ch, /* flags */ 0);
+ }
+}
+
+static void
+erase_displayed_memory (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int row;
+
+ /* EDM Erase Displayed Memory -- 001 c10f 010 1100 */
+
+ switch (ch->mode) {
+ case _VBI_CC608_MODE_UNKNOWN:
+ /* We have not received EOC, RCL, RDC or RUx yet, but
+ ch is valid. */
+ break;
+
+ case _VBI_CC608_MODE_ROLL_UP:
+ row = ch->curr_row;
+ if (0 != (ch->dirty[ch->displayed_buffer] & (1 << row)))
+ stream_event (cd, ch, row, row);
+ break;
+
+ case _VBI_CC608_MODE_PAINT_ON:
+ stream_event_if_changed (cd, ch);
+ break;
+
+ case _VBI_CC608_MODE_POP_ON:
+ /* Nothing to do. */
+ break;
+
+ case _VBI_CC608_MODE_TEXT:
+ /* Not reached. (ch is a caption channel.) */
+ return;
+ }
+
+ /* May send a display event. */
+ erase_memory (cd, ch, ch->displayed_buffer);
+}
+
+static void
+text_restart (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int curr_buffer;
+ unsigned int row;
+
+ /* TR Text Restart -- 001 c10f 010 1010 */
+
+ curr_buffer = ch->displayed_buffer;
+ row = ch->curr_row;
+
+ /* ch->mode is invariably VBI_CC608_MODE_TEXT. */
+
+ if (0 != (ch->dirty[curr_buffer] & (1 << row))) {
+ stream_event (cd, ch, row, row);
+ }
+
+ /* EIA 608-B Section 7.4. */
+ /* May send a display event. */
+ erase_memory (cd, ch, ch->displayed_buffer);
+
+ /* EIA 608-B Section 7.4. */
+ ch->curr_row = FIRST_ROW;
+ ch->curr_column = FIRST_COLUMN;
+}
+
+static void
+resume_direct_captioning (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int curr_buffer;
+ unsigned int row;
+
+ /* RDC Resume Direct Captioning -- 001 c10f 010 1001 */
+
+ /* 47 CFR 15.119 (f)(1)(x), (f)(2)(vi) and EIA 608-B Annex
+ B.7: Does not erase memory, does not move the cursor when
+ resuming after a Text transmission.
+
+ XXX If ch->mode is unknown, roll-up or pop-on, what is
+ expected if no PAC is received between RDC and the text? */
+
+ curr_buffer = ch->displayed_buffer;
+ row = ch->curr_row;
+
+ switch (ch->mode) {
+ case _VBI_CC608_MODE_ROLL_UP:
+ if (0 != (ch->dirty[curr_buffer] & (1 << row)))
+ stream_event (cd, ch, row, row);
+
+ /* fall through */
+
+ case _VBI_CC608_MODE_UNKNOWN:
+ case _VBI_CC608_MODE_POP_ON:
+ /* No change since last stream_event(). */
+ memcpy (ch->buffer[2], ch->buffer[curr_buffer], sizeof (ch->buffer[2]));
+ break;
+
+ case _VBI_CC608_MODE_PAINT_ON:
+ /* Mode continues. */
+ break;
+
+ case _VBI_CC608_MODE_TEXT:
+ /* Not reached. (ch is a caption channel.) */
+ return;
+ }
+
+ ch->mode = _VBI_CC608_MODE_PAINT_ON;
+}
+
+static void
+resize_window (_vbi_cc608_decoder * cd,
+ struct channel *ch, unsigned int new_rows)
+{
+ unsigned int curr_buffer;
+ unsigned int max_rows;
+ unsigned int old_rows;
+ unsigned int row1;
+
+ curr_buffer = ch->displayed_buffer;
+
+ /* Shortcut. */
+ if (0 == ch->dirty[curr_buffer])
+ return;
+
+ row1 = ch->curr_row + 1;
+ max_rows = row1 - FIRST_ROW;
+ old_rows = MIN (ch->window_rows, max_rows);
+ new_rows = MIN (new_rows, max_rows);
+
+ /* Nothing to do unless the window shrinks. */
+ if (0 == new_rows || new_rows >= old_rows)
+ return;
+
+ memset (&ch->buffer[curr_buffer][row1 - old_rows][0], 0, (old_rows - new_rows)
+ * sizeof (ch->buffer[0][0]));
+
+ ch->dirty[curr_buffer] &= -1 << (row1 - new_rows);
+
+ display_event (cd, ch, /* flags */ 0);
+}
+
+static void
+roll_up_caption (_vbi_cc608_decoder * cd, struct channel *ch, unsigned int c2)
+{
+ unsigned int window_rows;
+
+ /* Roll-Up Captions -- 001 c10f 010 01xx */
+
+ window_rows = (c2 & 7) - 3; /* 2, 3, 4 */
+
+ switch (ch->mode) {
+ case _VBI_CC608_MODE_ROLL_UP:
+ /* 47 CFR 15.119 (f)(1)(iv). */
+ /* May send a display event. */
+ resize_window (cd, ch, window_rows);
+
+ /* fall through */
+
+ case _VBI_CC608_MODE_UNKNOWN:
+ ch->mode = _VBI_CC608_MODE_ROLL_UP;
+ ch->window_rows = window_rows;
+
+ /* 47 CFR 15.119 (f)(1)(ix): No cursor movements,
+ no memory erasing. */
+
+ break;
+
+ case _VBI_CC608_MODE_PAINT_ON:
+ stream_event_if_changed (cd, ch);
+
+ /* fall through */
+
+ case _VBI_CC608_MODE_POP_ON:
+ ch->mode = _VBI_CC608_MODE_ROLL_UP;
+ ch->window_rows = window_rows;
+
+ /* 47 CFR 15.119 (f)(1)(ii). */
+ ch->curr_row = LAST_ROW;
+ ch->curr_column = FIRST_COLUMN;
+
+ /* 47 CFR 15.119 (f)(1)(x). */
+ /* May send a display event. */
+ erase_memory (cd, ch, ch->displayed_buffer);
+ erase_memory (cd, ch, ch->displayed_buffer ^ 1);
+
+ break;
+
+ case _VBI_CC608_MODE_TEXT:
+ /* Not reached. (ch is a caption channel.) */
+ return;
+ }
+}
+
+static void
+delete_to_end_of_row (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int curr_buffer;
+ unsigned int row;
+
+ /* DER Delete To End Of Row -- 001 c10f 010 0100 */
+
+ /* 47 CFR 15.119 (f)(1)(vii), (f)(2)(iii), (f)(3)(ii) and EIA
+ 608-B Section 7.4: In all caption modes and Text mode
+ "[the] Delete to End of Row command will erase from memory
+ any characters or control codes starting at the current
+ cursor location and in all columns to its right on the same
+ row." */
+
+ curr_buffer = ch->displayed_buffer ^ (_VBI_CC608_MODE_POP_ON == ch->mode);
+
+ row = ch->curr_row;
+
+ /* No event if the row contains only TRANSPARENT_SPACEs. */
+ if (0 != (ch->dirty[curr_buffer] & (1 << row))) {
+ unsigned int column;
+ unsigned int i;
+ uint16_t c;
+
+ column = ch->curr_column;
+
+ memset (&ch->buffer[curr_buffer][row][column], 0, (LAST_COLUMN - column + 1)
+ * sizeof (ch->buffer[0][0][0]));
+
+ c = 0;
+ for (i = FIRST_COLUMN; i < column; ++i)
+ c |= ch->buffer[curr_buffer][row][i];
+
+ ch->dirty[curr_buffer] &= ~((0 == c) << row);
+
+ display_event (cd, ch, /* flags */ 0);
+ }
+}
+
+static void
+backspace (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int curr_buffer;
+ unsigned int row;
+ unsigned int column;
+
+ /* BS Backspace -- 001 c10f 010 0001 */
+
+ /* 47 CFR Section 15.119 (f)(1)(vi), (f)(2)(ii), (f)(3)(i) and
+ EIA 608-B Section 7.4. */
+ column = ch->curr_column;
+ if (column <= FIRST_COLUMN)
+ return;
+
+ ch->curr_column = --column;
+
+ curr_buffer = ch->displayed_buffer ^ (_VBI_CC608_MODE_POP_ON == ch->mode);
+
+ row = ch->curr_row;
+
+ /* No event if there's no visible effect. */
+ if (0 != ch->buffer[curr_buffer][row][column]) {
+ unsigned int i;
+ uint16_t c;
+
+ /* 47 CFR 15.119 (f), (f)(1)(vi), (f)(2)(ii) and EIA
+ 608-B Section 7.4. */
+ ch->buffer[curr_buffer][row][column] = 0;
+
+ c = 0;
+ for (i = FIRST_COLUMN; i <= LAST_COLUMN; ++i)
+ c |= ch->buffer[curr_buffer][row][i];
+
+ ch->dirty[curr_buffer] &= ~((0 == c) << row);
+
+ display_event (cd, ch, /* flags */ 0);
+ }
+}
+
+static void
+resume_caption_loading (_vbi_cc608_decoder * cd, struct channel *ch)
+{
+ unsigned int row;
+
+ /* RCL Resume Caption Loading -- 001 c10f 010 0000 */
+
+ switch (ch->mode) {
+ case _VBI_CC608_MODE_UNKNOWN:
+ case _VBI_CC608_MODE_POP_ON:
+ break;
+
+ case _VBI_CC608_MODE_ROLL_UP:
+ row = ch->curr_row;
+ if (0 != (ch->dirty[ch->displayed_buffer] & (1 << row)))
+ stream_event (cd, ch, row, row);
+ break;
+
+ case _VBI_CC608_MODE_PAINT_ON:
+ stream_event_if_changed (cd, ch);
+ break;
+
+ case _VBI_CC608_MODE_TEXT:
+ /* Not reached. (ch is a caption channel.) */
+ return;
+ }
+
+ /* 47 CFR 15.119 (f)(1)(x): Does not erase memory.
+ (f)(2)(iv): Cursor position remains unchanged. */
+
+ ch->mode = _VBI_CC608_MODE_POP_ON;
+}
+
+/* Note curr_ch is invalid if UNKNOWN_CHANNEL == cd->cc.curr_ch_num. */
+static struct channel *
+switch_channel (_vbi_cc608_decoder * cd,
+ struct channel *curr_ch, vbi_pgno new_ch_num, enum field_num f)
+{
+ struct channel *new_ch;
+
+ if (UNKNOWN_CHANNEL != cd->curr_ch_num[f]
+ && _VBI_CC608_MODE_UNKNOWN != curr_ch->mode) {
+ /* XXX Force a display update if we do not send events
+ on every display change. */
+ }
+
+ cd->curr_ch_num[f] = new_ch_num;
+ new_ch = &cd->channel[new_ch_num - VBI_CAPTION_CC1];
+
+ return new_ch;
+}
+
+/* Note ch is invalid if UNKNOWN_CHANNEL == cd->cc.curr_ch_num[f]. */
+static void
+misc_control_code (_vbi_cc608_decoder * cd,
+ struct channel *ch, unsigned int c2, unsigned int ch_num0, enum field_num f)
+{
+ unsigned int new_ch_num;
+
+ /* Misc Control Codes -- 001 c10f 010 xxxx */
+
+ /* c = channel (0 -> CC1/CC3/T1/T3, 1 -> CC2/CC4/T2/T4)
+ -- 47 CFR Section 15.119, EIA 608-B Section 7.7.
+ f = field (0 -> F1, 1 -> F2)
+ -- EIA 608-B Section 8.4, 8.5. */
+
+ /* XXX The f flag is intended to detect accidential field
+ swapping and we should use it for that purpose. */
+
+ switch (c2 & 15) {
+ case 0: /* RCL Resume Caption Loading -- 001 c10f 010 0000 */
+ /* 47 CFR 15.119 (f)(2) and EIA 608-B Section 7.7. */
+ new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3);
+ ch = switch_channel (cd, ch, new_ch_num, f);
+ resume_caption_loading (cd, ch);
+ break;
+
+ case 1: /* BS Backspace -- 001 c10f 010 0001 */
+ if (UNKNOWN_CHANNEL == cd->curr_ch_num[f]
+ || _VBI_CC608_MODE_UNKNOWN == ch->mode)
+ break;
+ backspace (cd, ch);
+ break;
+
+ case 2: /* reserved (formerly AOF Alarm Off) */
+ case 3: /* reserved (formerly AON Alarm On) */
+ break;
+
+ case 4: /* DER Delete To End Of Row -- 001 c10f 010 0100 */
+ if (UNKNOWN_CHANNEL == cd->curr_ch_num[f]
+ || _VBI_CC608_MODE_UNKNOWN == ch->mode)
+ break;
+ delete_to_end_of_row (cd, ch);
+ break;
+
+ case 5: /* RU2 */
+ case 6: /* RU3 */
+ case 7: /* RU4 Roll-Up Captions -- 001 c10f 010 01xx */
+ /* 47 CFR 15.119 (f)(1) and EIA 608-B Section 7.7. */
+ new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3);
+ ch = switch_channel (cd, ch, new_ch_num, f);
+ roll_up_caption (cd, ch, c2);
+ break;
+
+ case 8: /* FON Flash On -- 001 c10f 010 1000 */
+ if (UNKNOWN_CHANNEL == cd->curr_ch_num[f]
+ || _VBI_CC608_MODE_UNKNOWN == ch->mode)
+ break;
+
+ /* 47 CFR 15.119 (h)(1)(i): Spacing attribute. */
+ put_char (cd, ch, 0x1428,
+ /* displayable */ FALSE,
+ /* backspace */ FALSE);
+ break;
+
+ case 9: /* RDC Resume Direct Captioning -- 001 c10f 010 1001 */
+ /* 47 CFR 15.119 (f)(3) and EIA 608-B Section 7.7. */
+ new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3);
+ ch = switch_channel (cd, ch, new_ch_num, f);
+ resume_direct_captioning (cd, ch);
+ break;
+
+ case 10: /* TR Text Restart -- 001 c10f 010 1010 */
+ /* EIA 608-B Section 7.4. */
+ new_ch_num = VBI_CAPTION_T1 + (ch_num0 & 3);
+ ch = switch_channel (cd, ch, new_ch_num, f);
+ text_restart (cd, ch);
+ break;
+
+ case 11: /* RTD Resume Text Display -- 001 c10f 010 1011 */
+ /* EIA 608-B Section 7.4. */
+ new_ch_num = VBI_CAPTION_T1 + (ch_num0 & 3);
+ ch = switch_channel (cd, ch, new_ch_num, f);
+ /* ch->mode is invariably VBI_CC608_MODE_TEXT. */
+ break;
+
+ case 12: /* EDM Erase Displayed Memory -- 001 c10f 010 1100 */
+ /* 47 CFR 15.119 (f). EIA 608-B Section 7.7 and Annex
+ B.7: "[The] command shall be acted upon as
+ appropriate for caption processing without
+ terminating the Text Mode data stream." */
+
+ /* We need not check cd->curr_ch_num because bit 2 is
+ implied, bit 1 is the known field number and bit 0
+ is coded in the control code. */
+ ch = &cd->channel[ch_num0 & 3];
+
+ erase_displayed_memory (cd, ch);
+
+ break;
+
+ case 13: /* CR Carriage Return -- 001 c10f 010 1101 */
+ if (UNKNOWN_CHANNEL == cd->curr_ch_num[f])
+ break;
+ carriage_return (cd, ch);
+ break;
+
+ case 14: /* ENM Erase Non-Displayed Memory -- 001 c10f 010 1110 */
+ /* 47 CFR 15.119 (f)(2)(v). EIA 608-B Section 7.7 and
+ Annex B.7: "[The] command shall be acted upon as
+ appropriate for caption processing without
+ terminating the Text Mode data stream." */
+
+ /* See EDM. */
+ ch = &cd->channel[ch_num0 & 3];
+
+ erase_memory (cd, ch, ch->displayed_buffer ^ 1);
+
+ break;
+
+ case 15: /* EOC End Of Caption -- 001 c10f 010 1111 */
+ /* 47 CFR 15.119 (f), (f)(2), (f)(3)(iv) and EIA 608-B
+ Section 7.7, Annex C.11. */
+ new_ch_num = VBI_CAPTION_CC1 + (ch_num0 & 3);
+ ch = switch_channel (cd, ch, new_ch_num, f);
+ end_of_caption (cd, ch);
+ break;
+ }
+}
+
+static void
+move_window (_vbi_cc608_decoder * cd,
+ struct channel *ch, unsigned int new_base_row)
+{
+ uint8_t *base;
+ unsigned int curr_buffer;
+ unsigned int bytes_per_row;
+ unsigned int old_max_rows;
+ unsigned int new_max_rows;
+ unsigned int copy_bytes;
+ unsigned int erase_begin;
+ unsigned int erase_end;
+
+ curr_buffer = ch->displayed_buffer;
+
+ /* Shortcut and no event if we do not move the window or the
+ buffer contains only TRANSPARENT_SPACEs. */
+ if (new_base_row == ch->curr_row || 0 == ch->dirty[curr_buffer])
+ return;
+
+ base = (void *) &ch->buffer[curr_buffer][FIRST_ROW][0];
+ bytes_per_row = sizeof (ch->buffer[0][0]);
+
+ old_max_rows = ch->curr_row + 1 - FIRST_ROW;
+ new_max_rows = new_base_row + 1 - FIRST_ROW;
+ copy_bytes = MIN (MIN (old_max_rows, new_max_rows),
+ ch->window_rows) * bytes_per_row;
+
+ if (new_base_row < ch->curr_row) {
+ erase_begin = (new_base_row + 1) * bytes_per_row;
+ erase_end = (ch->curr_row + 1) * bytes_per_row;
+
+ memmove (base + erase_begin - copy_bytes,
+ base + erase_end - copy_bytes, copy_bytes);
+
+ ch->dirty[curr_buffer] >>= ch->curr_row - new_base_row;
+ } else {
+ erase_begin = (ch->curr_row + 1) * bytes_per_row - copy_bytes;
+ erase_end = (new_base_row + 1) * bytes_per_row - copy_bytes;
+
+ memmove (base + erase_end, base + erase_begin, copy_bytes);
+
+ ch->dirty[curr_buffer] <<= new_base_row - ch->curr_row;
+ ch->dirty[curr_buffer] &= ALL_ROWS_MASK;
+ }
+
+ memset (base + erase_begin, 0, erase_end - erase_begin);
+
+ display_event (cd, ch, /* flags */ 0);
+}
+
+static void
+preamble_address_code (_vbi_cc608_decoder * cd,
+ struct channel *ch, unsigned int c1, unsigned int c2)
+{
+ unsigned int row;
+
+ /* PAC Preamble Address Codes -- 001 crrr 1ri xxxu */
+
+ row = pac_row_map[(c1 & 7) * 2 + ((c2 >> 5) & 1)];
+ if ((int) row < 0)
+ return;
+
+ switch (ch->mode) {
+ case _VBI_CC608_MODE_UNKNOWN:
+ return;
+
+ case _VBI_CC608_MODE_ROLL_UP:
+ /* EIA 608-B Annex C.4. */
+ if (ch->window_rows > row + 1)
+ row = ch->window_rows - 1;
+
+ /* 47 CFR Section 15.119 (f)(1)(ii). */
+ /* May send a display event. */
+ move_window (cd, ch, row);
+
+ ch->curr_row = row;
+
+ break;
+
+ case _VBI_CC608_MODE_PAINT_ON:
+ stream_event_if_changed (cd, ch);
+
+ /* fall through */
+
+ case _VBI_CC608_MODE_POP_ON:
+ /* XXX 47 CFR 15.119 (f)(2)(i), (f)(3)(i): In Pop-on
+ and paint-on mode "Preamble Address Codes can be
+ used to move the cursor around the screen in random
+ order to place captions on Rows 1 to 15." We do not
+ have a limit on the number of displayable rows, but
+ as EIA 608-B Annex C.6 points out, if more than
+ four rows must be displayed they were probably
+ received in error and we should respond
+ accordingly. */
+
+ /* 47 CFR Section 15.119 (d)(1)(i) and EIA 608-B Annex
+ C.7. */
+ ch->curr_row = row;
+
+ break;
+
+ case _VBI_CC608_MODE_TEXT:
+ /* 47 CFR 15.119 (e)(1) and EIA 608-B Section 7.4:
+ Does not change the cursor row. */
+ break;
+ }
+
+ if (c2 & 0x10) {
+ /* 47 CFR 15.119 (e)(1)(i) and EIA 608-B Table 71. */
+ ch->curr_column = FIRST_COLUMN + (c2 & 0x0E) * 2;
+ }
+
+ /* PAC is a non-spacing attribute for the next character, see
+ put_char(). */
+ ch->last_pac = 0x1000 | c2;
+}
+
+static void
+control_code (_vbi_cc608_decoder * cd,
+ unsigned int c1, unsigned int c2, enum field_num f)
+{
+ struct channel *ch;
+ unsigned int ch_num0;
+
+ if (CC608_DECODER_LOG_INPUT) {
+ fprintf (stdout, "%s:%u: %s c1=%02x c2=%02x f=%d\n",
+ __FILE__, __LINE__, __FUNCTION__, c1, c2, f);
+ }
+
+ /* b2: Caption / text,
+ b1: field 1 / 2,
+ b0 (lsb): primary / secondary channel. */
+ ch_num0 = (((cd->curr_ch_num[f] - VBI_CAPTION_CC1) & 4)
+ + f * 2 + ((c1 >> 3) & 1));
+
+ /* Note ch is invalid if UNKNOWN_CHANNEL ==
+ cd->curr_ch_num[f]. */
+ ch = &cd->channel[ch_num0];
+
+ if (c2 >= 0x40) {
+ /* Preamble Address Codes -- 001 crrr 1ri xxxu */
+ if (UNKNOWN_CHANNEL != cd->curr_ch_num[f])
+ preamble_address_code (cd, ch, c1, c2);
+ return;
+ }
+
+ switch (c1 & 7) {
+ case 0:
+ if (UNKNOWN_CHANNEL == cd->curr_ch_num[f]
+ || _VBI_CC608_MODE_UNKNOWN == ch->mode)
+ break;
+
+ if (c2 < 0x30) {
+ /* Backgr. Attr. Codes -- 001 c000 010 xxxt */
+ /* EIA 608-B Section 6.2. */
+ put_char (cd, ch, 0x1000 | c2,
+ /* displayable */ FALSE,
+ /* backspace */ TRUE);
+ } else {
+ /* Undefined. */
+ }
+
+ break;
+
+ case 1:
+ if (UNKNOWN_CHANNEL == cd->curr_ch_num[f]
+ || _VBI_CC608_MODE_UNKNOWN == ch->mode)
+ break;
+
+ if (c2 < 0x30) {
+ /* Mid-Row Codes -- 001 c001 010 xxxu */
+ /* 47 CFR 15.119 (h)(1)(i): Spacing attribute. */
+ put_char (cd, ch, 0x1100 | c2,
+ /* displayable */ FALSE,
+ /* backspace */ FALSE);
+ } else {
+ /* Special Characters -- 001 c001 011 xxxx */
+ if (0x39 == c2) {
+ /* Transparent space. */
+ put_char (cd, ch, 0,
+ /* displayable */ FALSE,
+ /* backspace */ FALSE);
+ } else {
+ put_char (cd, ch, 0x1100 | c2,
+ /* displayable */ TRUE,
+ /* backspace */ FALSE);
+ }
+ }
+
+ break;
+
+ case 2:
+ case 3: /* Extended Character Set -- 001 c01x 01x xxxx */
+ if (UNKNOWN_CHANNEL == cd->curr_ch_num[f]
+ || _VBI_CC608_MODE_UNKNOWN == ch->mode)
+ break;
+
+ /* EIA 608-B Section 6.4.2. */
+ put_char (cd, ch, (c1 * 256 + c2) & 0x777F,
+ /* displayable */ TRUE,
+ /* backspace */ TRUE);
+ break;
+
+ case 4:
+ case 5:
+ if (c2 < 0x30) {
+ /* Misc. Control Codes -- 001 c10f 010 xxxx */
+ misc_control_code (cd, ch, c2, ch_num0, f);
+ } else {
+ /* Undefined. */
+ }
+
+ break;
+
+ case 6: /* reserved */
+ break;
+
+ case 7: /* Extended control codes -- 001 c111 01x xxxx */
+ if (UNKNOWN_CHANNEL == cd->curr_ch_num[f]
+ || _VBI_CC608_MODE_UNKNOWN == ch->mode)
+ break;
+
+ ext_control_code (cd, ch, c2);
+
+ break;
+ }
+}
+
+static vbi_bool
+characters (_vbi_cc608_decoder * cd, struct channel *ch, int c)
+{
+ if (CC608_DECODER_LOG_INPUT) {
+ fprintf (stdout, "%s:%u: %s c=0x%02x='%c'\n",
+ __FILE__, __LINE__, __FUNCTION__, c, _vbi_to_ascii (c));
+ }
+
+ if (0 == c) {
+ if (_VBI_CC608_MODE_UNKNOWN == ch->mode)
+ return TRUE;
+
+ /* XXX After x NUL characters (presumably a caption
+ pause), force a display update if we do not send
+ events on every display change. */
+
+ return TRUE;
+ }
+
+ if (c < 0x20) {
+ /* Parity error or invalid data. */
+
+ if (c < 0 && _VBI_CC608_MODE_UNKNOWN != ch->mode) {
+ /* 47 CFR Section 15.119 (j)(1). */
+ put_char (cd, ch, 0x7F,
+ /* displayable */ TRUE,
+ /* backspace */ FALSE);
+ }
+
+ return FALSE;
+ }
+
+ if (_VBI_CC608_MODE_UNKNOWN != ch->mode) {
+ put_char (cd, ch, c,
+ /* displayable */ TRUE,
+ /* backspace */ FALSE);
+ }
+
+ return TRUE;
+}
+
+/**
+ * @param cd Caption decoder allocated with _vbi_cc608_decoder_new().
+ * @param buffer A caption byte pair with parity bits.
+ * @param line ITU-R line number this data originated from,
+ * usually 21 or 284.
+ * @param capture_time System time in seconds when the sliced data was
+ * captured.
+ * @param pts ISO 13818-1 Presentation Time Stamp of the sliced
+ * data. @a pts counts 1/90000 seconds from an arbitrary point in the
+ * video stream. Only the 33 least significant bits have to be valid.
+ * If @a pts is negative the function converts @a capture_time to a
+ * PTS.
+ *
+ * This function decodes two bytes of Closed Caption data and updates
+ * the decoder state. It may send one VBI_EVENT_CC608 and one or more
+ * VBI_EVENT_CC608_STREAM.
+ *
+ * @returns
+ * @c FALSE if the caption byte pair contained errors.
+ */
+vbi_bool
+_vbi_cc608_decoder_feed (_vbi_cc608_decoder * cd,
+ const uint8_t buffer[2],
+ unsigned int line, double capture_time, int64_t pts)
+{
+ int c1, c2;
+ enum field_num f;
+ vbi_bool all_successful;
+
+ assert (NULL != cd);
+
+ if (0) {
+ fprintf (stdout, "%s:%u: %s "
+ "buffer={ 0x%02x 0x%02x '%c%c' } "
+ "line=%3d capture_time=%f "
+ "pts=%" PRId64 "\n",
+ __FILE__, __LINE__, __FUNCTION__,
+ buffer[0] & 0x7F,
+ buffer[1] & 0x7F,
+ _vbi_to_ascii (buffer[0]),
+ _vbi_to_ascii (buffer[1]), line, capture_time, pts);
+ }
+
+ f = FIELD_1;
+
+ switch (line) {
+ case 21: /* NTSC */
+ case 22: /* PAL/SECAM? */
+ break;
+
+ case 284: /* NTSC */
+ f = FIELD_2;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ cd->timestamp.sys = capture_time;
+
+ if (pts < 0)
+ pts = (int64_t) (capture_time * 90000);
+
+ /* Modulo 1 << 33 guaranteed in VBI_EVENT_CC608_STREAM dox. */
+ cd->timestamp.pts = pts & (((int64_t) 1 << 33) - 1);
+
+ /* XXX deferred reset here */
+
+ if (0 && FIELD_1 == f) {
+ _vbi_cc608_dump (stderr, buffer[0], buffer[1]);
+ }
+
+ c1 = vbi_unpar8 (buffer[0]);
+ c2 = vbi_unpar8 (buffer[1]);
+
+ all_successful = TRUE;
+
+ /* See 47 CFR 15.119 (2)(i)(4). EIA 608-B Section 8.3: Caption
+ control codes on field 2 may repeat as on field 1. Section
+ 8.6.2: XDS control codes shall not repeat. */
+
+ if (unlikely (c1 < 0)) {
+ goto parity_error;
+ } else if (c1 == cd->expect_ctrl[f][0]
+ && c2 == cd->expect_ctrl[f][1]) {
+ /* Already acted upon. */
+ cd->expect_ctrl[f][0] = -1;
+ goto finish;
+ }
+
+ if (c1 >= 0x10 && c1 < 0x20) {
+ /* Caption control code. */
+
+ /* There's no XDS on field 1, we just
+ use an array to save a branch. */
+ cd->in_xds[f] = FALSE;
+
+ /* 47 CFR Section 15.119 (i)(1), (i)(2). */
+ if (c2 < 0x20) {
+ /* Parity error or invalid control code.
+ Let's hope this code will repeat. */
+ goto parity_error;
+ }
+
+ control_code (cd, c1, c2, f);
+
+ if (cd->event_pending) {
+ display_event (cd, cd->event_pending,
+ /* flags */ 0);
+ cd->event_pending = NULL;
+ }
+
+ cd->expect_ctrl[f][0] = c1;
+ cd->expect_ctrl[f][1] = c2;
+ } else {
+ cd->expect_ctrl[f][0] = -1;
+
+ if (c1 < 0x10) {
+ if (FIELD_1 == f) {
+ /* 47 CFR Section 15.119 (i)(1): "If the
+ non-printing character in the pair is
+ in the range 00h to 0Fh, that character
+ alone will be ignored and the second
+ character will be treated normally." */
+ c1 = 0;
+ } else if (0x0F == c1) {
+ /* XDS packet terminator. */
+ cd->in_xds[FIELD_2] = FALSE;
+ goto finish;
+ } else if (c1 >= 0x01) {
+ /* XDS packet start or continuation.
+ EIA 608-B Section 7.7, 8.5: Also
+ interrupts a Text mode
+ transmission. */
+ cd->in_xds[FIELD_2] = TRUE;
+ goto finish;
+ }
+ }
+
+ {
+ struct channel *ch;
+ vbi_pgno ch_num;
+
+ ch_num = cd->curr_ch_num[f];
+ if (UNKNOWN_CHANNEL == ch_num)
+ goto finish;
+
+ ch_num = ((ch_num - VBI_CAPTION_CC1) & 5) + f * 2;
+ ch = &cd->channel[ch_num];
+
+ all_successful &= characters (cd, ch, c1);
+ all_successful &= characters (cd, ch, c2);
+
+ if (cd->event_pending) {
+ display_event (cd, cd->event_pending,
+ /* flags */ 0);
+ cd->event_pending = NULL;
+ }
+ }
+ }
+
+finish:
+ cd->error_history = cd->error_history * 2 + all_successful;
+
+ return all_successful;
+
+parity_error:
+ cd->expect_ctrl[f][0] = -1;
+
+ /* XXX Some networks stupidly transmit 0x0000 instead of
+ 0x8080 as filler. Perhaps we shouldn't take that as a
+ serious parity error. */
+ cd->error_history *= 2;
+
+ return FALSE;
+}
+
+/**
+ * @param cd Caption decoder allocated with _vbi_cc608_decoder_new().
+ * @param sliced Sliced VBI data.
+ * @param n_lines Number of lines in the @a sliced array.
+ * @param capture_time System time in seconds when the sliced data was
+ * captured.
+ * @param pts ISO 13818-1 Presentation Time Stamp of all elements
+ * in the sliced data array. @a pts counts 1/90000 seconds from an
+ * arbitrary point in the video stream. Only the 33 least significant
+ * bits have to be valid. If @a pts is negative the function
+ * converts @a capture_time to a PTS.
+ *
+ * This function works like _vbi_cc608_decoder_feed() but operates
+ * on sliced VBI data and filters out @c VBI_SLICED_CAPTION_525.
+ */
+vbi_bool
+_vbi_cc608_decoder_feed_frame (_vbi_cc608_decoder * cd,
+ const vbi_sliced * sliced,
+ unsigned int n_lines, double capture_time, int64_t pts)
+{
+ const vbi_sliced *end;
+
+ assert (NULL != cd);
+ assert (NULL != sliced);
+
+ for (end = sliced + n_lines; sliced < end; ++sliced) {
+ if (sliced->id & VBI_SLICED_CAPTION_525) {
+ if (!_vbi_cc608_decoder_feed (cd,
+ sliced->data, sliced->line, capture_time, pts))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * @param cd Caption decoder allocated with _vbi_cc608_decoder_new().
+ * @param callback Function to be called on events.
+ * @param user_data User pointer passed through to the @a callback
+ * function.
+ *
+ * Removes an event handler from the caption decoder, if a handler with
+ * this @a callback and @a user_data has been registered.
+ */
+void _vbi_cc608_decoder_remove_event_handler
+ (_vbi_cc608_decoder * cd, vbi_event_handler callback, void *user_data)
+{
+ _vbi_event_handler_list_remove_by_callback (&cd->handlers,
+ callback, user_data);
+}
+
+/**
+ * @param cd Caption decoder allocated with _vbi_cc608_decoder_new().
+ * @param event_mask Set of events the handler is waiting for,
+ * VBI_EVENT_CC608 or VBI_EVENT_CC608_STREAM.
+ * @param callback Function to be called on events by
+ * _vbi_cc608_decoder_feed().
+ * @param user_data User pointer passed through to the @a callback
+ * function.
+ *
+ * Adds a new event handler to the caption decoder. When the @a
+ * callback with this @a user_data is already registered the function
+ * changes the set of events the callback function will receive in the
+ * future.
+ *
+ * Any number of handlers can be added, also different handlers for the
+ * same event, which will be called in registration order.
+ *
+ * @returns
+ * @c FALSE on failure (out of memory).
+ */
+vbi_bool
+ _vbi_cc608_decoder_add_event_handler
+ (_vbi_cc608_decoder * cd,
+ unsigned int event_mask, vbi_event_handler callback, void *user_data)
+{
+ event_mask &= (_VBI_EVENT_CC608 | _VBI_EVENT_CC608_STREAM);
+
+ if (0 == event_mask) {
+ _vbi_event_handler_list_remove_by_callback (&cd->handlers,
+ callback, user_data);
+ return TRUE;
+ }
+
+ if (NULL != _vbi_event_handler_list_add (&cd->handlers,
+ event_mask, callback, user_data)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * @param cd Caption decoder allocated with _vbi_cc608_decoder_new().
+ *
+ * Resets the caption decoder, useful for example after a channel
+ * change.
+ */
+void
+_vbi_cc608_decoder_reset (_vbi_cc608_decoder * cd)
+{
+ unsigned int ch_num;
+
+ assert (NULL != cd);
+
+ if (CC608_DECODER_LOG_INPUT) {
+ fprintf (stderr, "%s:%u: %s\n", __FILE__, __LINE__, __FUNCTION__);
+
+ }
+
+ for (ch_num = 0; ch_num < MAX_CHANNELS; ++ch_num) {
+ struct channel *ch;
+
+ ch = &cd->channel[ch_num];
+
+ if (ch_num <= 3) {
+ ch->mode = _VBI_CC608_MODE_UNKNOWN;
+
+ /* Plausible for roll-up mode. We don't
+ display text while the caption mode is
+ unknown and may choose more suitable
+ defaults when we receive a mode changing
+ control code. */
+ ch->curr_row = LAST_ROW;
+ ch->curr_column = FIRST_COLUMN;
+ ch->window_rows = 4;
+ } else {
+ ch->mode = _VBI_CC608_MODE_TEXT; /* invariable */
+
+ /* EIA 608-B Section 7.4: "When Text Mode has
+ initially been selected and the specified
+ Text memory is empty, the cursor starts at
+ the topmost row, Column 1." */
+ ch->curr_row = FIRST_ROW;
+ ch->curr_column = FIRST_COLUMN;
+ ch->window_rows = 0; /* n/a */
+ }
+
+ ch->displayed_buffer = 0;
+
+ ch->last_pac = 0;
+
+ CLEAR (ch->buffer);
+ CLEAR (ch->dirty);
+
+ timestamp_reset (&ch->timestamp);
+ timestamp_reset (&ch->timestamp_c0);
+ }
+
+ cd->curr_ch_num[0] = UNKNOWN_CHANNEL;
+ cd->curr_ch_num[1] = UNKNOWN_CHANNEL;
+
+ memset (cd->expect_ctrl, -1, sizeof (cd->expect_ctrl));
+
+ CLEAR (cd->in_xds);
+
+ cd->event_pending = NULL;
+}
+
+static void
+_vbi_cc608_decoder_destroy (_vbi_cc608_decoder * cd)
+{
+ assert (NULL != cd);
+
+ _vbi_event_handler_list_destroy (&cd->handlers);
+
+ CLEAR (*cd);
+}
+
+static void
+_vbi_cc608_decoder_init (_vbi_cc608_decoder * cd)
+{
+ assert (NULL != cd);
+
+ CLEAR (*cd);
+
+ _vbi_event_handler_list_init (&cd->handlers);
+
+ _vbi_cc608_decoder_reset (cd);
+
+ timestamp_reset (&cd->timestamp);
+}
+
+/**
+ * @param cd Caption decoder allocated with _vbi_cc608_decoder_new(),
+ * can be @a NULL.
+ *
+ * Frees all resources associated with @a cd.
+ */
+void
+_vbi_cc608_decoder_delete (_vbi_cc608_decoder * cd)
+{
+ if (NULL == cd)
+ return;
+
+ _vbi_cc608_decoder_destroy (cd);
+
+ vbi_free (cd);
+}
+
+/**
+ * Allocates a new EIA 608-B Closed Caption decoder.
+ *
+ * To enter caption data call the _vbi_cc608_decoder_feed()
+ * function. Decoded data is available through VBI_EVENT_CC608_STREAM
+ * and the _vbi_cc608_decoder_get_page() function.
+ *
+ * To be notified when new data is available call
+ * _vbi_cc608_decoder_add_event_handler().
+ *
+ * @returns
+ * Pointer to a newly allocated caption decoder which must be freed
+ * with _vbi_cc608_decoder_delete() when no longer needed. @c NULL
+ * on failure (out of memory).
+ */
+_vbi_cc608_decoder *
+_vbi_cc608_decoder_new (void)
+{
+ _vbi_cc608_decoder *cd;
+
+ cd = vbi_malloc (sizeof (*cd));
+
+ if (NULL != cd) {
+ _vbi_cc608_decoder_init (cd);
+ }
+
+ return cd;
+}
+
+/*
+Local variables:
+c-set-style: K&R
+c-basic-offset: 8
+End:
+*/