diff options
Diffstat (limited to 'gst/closedcaption/cc608_decoder.c')
-rw-r--r-- | gst/closedcaption/cc608_decoder.c | 2482 |
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: +*/ |