From fa8134ed1109e8dbc42a4fc74defdc668927fdaf Mon Sep 17 00:00:00 2001 From: Mathieu Duponchelle Date: Thu, 21 Feb 2019 19:05:03 +0100 Subject: [PATCH] Check in vbi encoder --- ext/closedcaption/hamm.h | 233 +++++++ ext/closedcaption/io-sim.c | 1143 +++++++++++++++++++++++++++++++++ ext/closedcaption/io-sim.h | 104 +++ ext/closedcaption/meson.build | 1 + 4 files changed, 1481 insertions(+) create mode 100644 ext/closedcaption/hamm.h create mode 100644 ext/closedcaption/io-sim.c create mode 100644 ext/closedcaption/io-sim.h diff --git a/ext/closedcaption/hamm.h b/ext/closedcaption/hamm.h new file mode 100644 index 0000000000..2fd8da7002 --- /dev/null +++ b/ext/closedcaption/hamm.h @@ -0,0 +1,233 @@ +/* + * libzvbi -- Error correction functions + * + * Copyright (C) 2001, 2002, 2003, 2004, 2007 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: hamm.h,v 1.16 2013-07-10 11:37:13 mschimek Exp $ */ + +#ifndef __ZVBI_HAMM_H__ +#define __ZVBI_HAMM_H__ + +#include /* uintN_t */ +#include "macros.h" + +VBI_BEGIN_DECLS + +/* Public */ + +extern const uint8_t _vbi_bit_reverse [256]; +extern const uint8_t _vbi_hamm8_fwd [16]; +extern const int8_t _vbi_hamm8_inv [256]; +extern const int8_t _vbi_hamm24_inv_par [3][256]; + +/** + * @addtogroup Error Error correction functions + * @ingroup Raw + * @brief Helper functions to decode sliced VBI data. + * @{ + */ + +/** + * @param c Unsigned byte. + * + * Reverses the bits of the argument. + * + * @returns + * Data bits 0 [msb] ... 7 [lsb]. + * + * @since 0.2.12 + */ +_vbi_inline unsigned int +vbi_rev8 (unsigned int c) +{ + return _vbi_bit_reverse[(uint8_t) c]; +} + +/** + * @param c Unsigned 16 bit word. + * + * Reverses (or "reflects") the bits of the argument. + * + * @returns + * Data bits 0 [msb] ... 15 [lsb]. + * + * @since 0.2.12 + */ +_vbi_inline unsigned int +vbi_rev16 (unsigned int c) +{ + return _vbi_bit_reverse[(uint8_t) c] * 256 + + _vbi_bit_reverse[(uint8_t)(c >> 8)]; +} + +/** + * @param p Pointer to a 16 bit word, last significant + * byte first. + * + * Reverses (or "reflects") the bits of the argument. + * + * @returns + * Data bits 0 [msb] ... 15 [lsb]. + * + * @since 0.2.12 + */ +_vbi_inline unsigned int +vbi_rev16p (const uint8_t * p) +{ + return _vbi_bit_reverse[p[0]] * 256 + + _vbi_bit_reverse[p[1]]; +} + +/** + * @param c Unsigned byte. + * + * @returns + * Changes the most significant bit of the byte + * to make the number of set bits odd. + * + * @since 0.2.12 + */ +_vbi_inline unsigned int +vbi_par8 (unsigned int c) +{ + c &= 255; + + /* if 0 == (inv_par[] & 32) change bit 7 of c. */ + c ^= 128 & ~(_vbi_hamm24_inv_par[0][c] << 2); + + return c; +} + +/** + * @param c Unsigned byte. + * + * @returns + * If the byte has odd parity (sum of bits modulo 2 is 1) the + * byte AND 127, otherwise a negative value. + * + * @since 0.2.12 + */ +_vbi_inline int +vbi_unpar8 (unsigned int c) +{ +/* Disabled until someone finds a reliable way + to test for cmov support at compile time. */ +#if 0 + int r = c & 127; + + /* This saves cache flushes and an explicit branch. */ + __asm__ (" testb %1,%1\n" + " cmovp %2,%0\n" + : "+&a" (r) : "c" (c), "rm" (-1)); + return r; +#endif + if (_vbi_hamm24_inv_par[0][(uint8_t) c] & 32) { + return c & 127; + } else { + /* The idea is to OR results together to find a parity + error in a sequence, rather than a test and branch on + each byte. */ + return -1; + } +} + +extern void +vbi_par (uint8_t * p, + unsigned int n); +extern int +vbi_unpar (uint8_t * p, + unsigned int n); + +/** + * @param c Integer between 0 ... 15. + * + * Encodes a nibble with Hamming 8/4 protection + * as specified in EN 300 706, Section 8.2. + * + * @returns + * Hamming encoded unsigned byte, lsb first transmitted. + * + * @since 0.2.12 + */ +_vbi_inline unsigned int +vbi_ham8 (unsigned int c) +{ + return _vbi_hamm8_fwd[c & 15]; +} + +/** + * @param c Hamming 8/4 protected byte, lsb first transmitted. + * + * Decodes a Hamming 8/4 protected byte + * as specified in EN 300 706, Section 8.2. + * + * @returns + * Data bits (D4 [msb] ... D1 [lsb]) or a negative + * value if the byte contained uncorrectable errors. + * + * @since 0.2.12 + */ +_vbi_inline int +vbi_unham8 (unsigned int c) +{ + return _vbi_hamm8_inv[(uint8_t) c]; +} + +/** + * @param p Pointer to a Hamming 8/4 protected 16 bit word, + * last significant byte first, lsb first transmitted. + * + * Decodes a Hamming 8/4 protected byte pair + * as specified in EN 300 706, Section 8.2. + * + * @returns + * Data bits D4 [msb] ... D1 of first byte and D4 ... D1 [lsb] + * of second byte, or a negative value if any of the bytes + * contained uncorrectable errors. + * + * @since 0.2.12 + */ +_vbi_inline int +vbi_unham16p (const uint8_t * p) +{ + return ((int) _vbi_hamm8_inv[p[0]]) + | (((int) _vbi_hamm8_inv[p[1]]) << 4); +} + +extern void +vbi_ham24p (uint8_t * p, + unsigned int c); +extern int +vbi_unham24p (const uint8_t * p) + _vbi_pure; + +/** @} */ + +/* Private */ + +VBI_END_DECLS + +#endif /* __ZVBI_HAMM_H__ */ + +/* +Local variables: +c-set-style: K&R +c-basic-offset: 8 +End: +*/ diff --git a/ext/closedcaption/io-sim.c b/ext/closedcaption/io-sim.c new file mode 100644 index 0000000000..8ba58ab225 --- /dev/null +++ b/ext/closedcaption/io-sim.c @@ -0,0 +1,1143 @@ +/* + * libzvbi -- VBI device simulation + * + * Copyright (C) 2004, 2007 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: io-sim.c,v 1.18 2009-12-14 23:43:40 mschimek Exp $ */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include /* sin(), log() */ +#include +#include /* isspace() */ +#include /* INT_MAX */ + +#include "misc.h" +#include "sliced.h" +#include "sampling_par.h" +#include "raw_decoder.h" +#include "hamm.h" + +# define sp_sample_format sampling_format +# define SAMPLES_PER_LINE(sp) \ + ((sp)->bytes_per_line / VBI_PIXFMT_BPP ((sp)->sampling_format)) +# define SYSTEM_525(sp) \ + (525 == (sp)->scanning) + +#include "io-sim.h" + +/** + * @addtogroup Rawenc Raw VBI encoder + * @ingroup Raw + * @brief Converting sliced VBI data to raw VBI images. + * + * These are functions converting sliced VBI data to raw VBI images as + * transmitted in the vertical blanking interval of analog video standards. + * They are mainly intended for tests of the libzvbi bit slicer and + * raw VBI decoder. + */ + +# define VBI_PIXFMT_RGB24_LE VBI_PIXFMT_RGB24 +# define VBI_PIXFMT_BGR24_LE VBI_PIXFMT_BGR24 +# define VBI_PIXFMT_RGBA24_LE VBI_PIXFMT_RGBA32_LE +# define VBI_PIXFMT_BGRA24_LE VBI_PIXFMT_BGRA32_LE +# define VBI_PIXFMT_RGBA24_BE VBI_PIXFMT_RGBA32_BE +# define VBI_PIXFMT_BGRA24_BE VBI_PIXFMT_BGRA32_BE +# define vbi_pixfmt_bytes_per_pixel VBI_PIXFMT_BPP + +#define PI 3.1415926535897932384626433832795029 + +#define PULSE(zero_level) \ +do { \ + if (0 == seq) { \ + raw[i] = SATURATE (zero_level, 0, 255); \ + } else if (3 == seq) { \ + raw[i] = SATURATE (zero_level + (int) signal_amp, \ + 0, 255); \ + } else if ((seq ^ bit) & 1) { /* down */ \ + double r = sin (q * tr - (PI / 2.0)); \ + r = r * r * signal_amp; \ + raw[i] = SATURATE (zero_level + (int) r, 0, 255); \ + } else { /* up */ \ + double r = sin (q * tr); \ + r = r * r * signal_amp; \ + raw[i] = SATURATE (zero_level + (int) r, 0, 255); \ + } \ +} while (0) + +#define PULSE_SEQ(zero_level) \ +do { \ + double tr; \ + unsigned int bit; \ + unsigned int byte; \ + unsigned int seq; \ + \ + tr = t - t1; \ + bit = tr * bit_rate; \ + byte = bit >> 3; \ + bit &= 7; \ + seq = (buf[byte] >> 7) + buf[byte + 1] * 2; \ + seq = (seq >> bit) & 3; \ + PULSE (zero_level); \ +} while (0) + +_vbi_inline void +vbi_sincos (double x, double *sinx, double *cosx) +{ + *sinx = sin (x); + *cosx = cos (x); +} + +#define vbi_log2(x) (log (x) / M_LN2) + +static void +signal_teletext (uint8_t * raw, + const vbi_sampling_par * sp, + int black_level, + double signal_amp, + double bit_rate, + unsigned int frc, unsigned int payload, const vbi_sliced * sliced) +{ + double bit_period = 1.0 / bit_rate; + /* Teletext System B: Sixth CRI pulse at 12 us + (+.5 b/c we start with a 0 bit). */ + double t1 = 12e-6 - 13 * bit_period; + double t2 = t1 + (payload * 8 + 24 + 1) * bit_period; + double q = (PI / 2.0) * bit_rate; + double sample_period = 1.0 / sp->sampling_rate; + unsigned int samples_per_line; + uint8_t buf[64]; + unsigned int i; + double t; + + buf[0] = 0x00; + buf[1] = 0x55; /* clock run-in */ + buf[2] = 0x55; + buf[3] = frc; + + memcpy (buf + 4, sliced->data, payload); + + buf[payload + 4] = 0x00; + + t = sp->offset / (double) sp->sampling_rate; + + samples_per_line = SAMPLES_PER_LINE (sp); + + for (i = 0; i < samples_per_line; ++i) { + if (t >= t1 && t < t2) + PULSE_SEQ (black_level); + + t += sample_period; + } +} + +static void +signal_vps (uint8_t * raw, + const vbi_sampling_par * sp, + int black_level, int white_level, const vbi_sliced * sliced) +{ + static const uint8_t biphase[] = { + 0xAA, 0x6A, 0x9A, 0x5A, + 0xA6, 0x66, 0x96, 0x56, + 0xA9, 0x69, 0x99, 0x59, + 0xA5, 0x65, 0x95, 0x55 + }; + double bit_rate = 15625 * 160 * 2; + double t1 = 12.5e-6 - .5 / bit_rate; + double t4 = t1 + ((4 + 13 * 2) * 8) / bit_rate; + double q = (PI / 2.0) * bit_rate; + double sample_period = 1.0 / sp->sampling_rate; + unsigned int samples_per_line; + double signal_amp = (0.5 / 0.7) * (white_level - black_level); + uint8_t buf[32]; + unsigned int i; + double t; + + CLEAR (buf); + + buf[1] = 0x55; /* 0101 0101 */ + buf[2] = 0x55; /* 0101 0101 */ + buf[3] = 0x51; /* 0101 0001 */ + buf[4] = 0x99; /* 1001 1001 */ + + for (i = 0; i < 13; ++i) { + unsigned int b = sliced->data[i]; + + buf[5 + i * 2] = biphase[b >> 4]; + buf[6 + i * 2] = biphase[b & 15]; + } + + buf[6 + 12 * 2] &= 0x7F; + + t = sp->offset / (double) sp->sampling_rate; + + samples_per_line = SAMPLES_PER_LINE (sp); + + for (i = 0; i < samples_per_line; ++i) { + if (t >= t1 && t < t4) + PULSE_SEQ (black_level); + + t += sample_period; + } +} + +static void +wss_biphase (uint8_t buf[32], const vbi_sliced * sliced) +{ + unsigned int bit; + unsigned int data; + unsigned int i; + + /* 29 bit run-in and 24 bit start code, lsb first. */ + + buf[0] = 0x00; + buf[1] = 0x1F; /* 0001 1111 */ + buf[2] = 0xC7; /* 1100 0111 */ + buf[3] = 0x71; /* 0111 0001 */ + buf[4] = 0x1C; /* 000 | 1 1100 */ + buf[5] = 0x8F; /* 1000 1111 */ + buf[6] = 0x07; /* 0000 0111 */ + buf[7] = 0x1F; /* 1 1111 */ + + bit = 8 + 29 + 24; + data = sliced->data[0] + sliced->data[1] * 256; + + for (i = 0; i < 14; ++i) { + static const unsigned int biphase[] = { 0x38, 0x07 }; + unsigned int byte; + unsigned int shift; + unsigned int seq; + + byte = bit >> 3; + shift = bit & 7; + bit += 6; + + seq = biphase[data & 1] << shift; + data >>= 1; + + assert (byte < 31); + + buf[byte] |= seq; + buf[byte + 1] = seq >> 8; + } +} + +static void +signal_wss_625 (uint8_t * raw, + const vbi_sampling_par * sp, + int black_level, int white_level, const vbi_sliced * sliced) +{ + double bit_rate = 15625 * 320; + double t1 = 11.0e-6 - .5 / bit_rate; + double t4 = t1 + (29 + 24 + 14 * 6 + 1) / bit_rate; + double q = (PI / 2.0) * bit_rate; + double sample_period = 1.0 / sp->sampling_rate; + double signal_amp = (0.5 / 0.7) * (white_level - black_level); + unsigned int samples_per_line; + uint8_t buf[32]; + unsigned int i; + double t; + + CLEAR (buf); + + wss_biphase (buf, sliced); + + t = sp->offset / (double) sp->sampling_rate; + + samples_per_line = SAMPLES_PER_LINE (sp); + + for (i = 0; i < samples_per_line; ++i) { + if (t >= t1 && t < t4) + PULSE_SEQ (black_level); + + t += sample_period; + } +} + +static void +signal_closed_caption (uint8_t * raw, + const vbi_sampling_par * sp, + int blank_level, + int white_level, + unsigned int flags, double bit_rate, const vbi_sliced * sliced) +{ + double D = 1.0 / bit_rate; + double t0 = 10.5e-6; /* CRI start half amplitude (EIA 608-B) */ + double t1 = t0 - .25 * D; /* CRI start, blanking level */ + double t2 = t1 + 7 * D; /* CRI 7 cycles */ + /* First start bit, left edge half amplitude, minus rise time. */ + double t3 = t0 + 6.5 * D - 120e-9; + double q1 = PI * bit_rate * 2; + /* Max. rise/fall time 240 ns (EIA 608-B). */ + double q2 = PI / 120e-9; + double signal_mean; + double signal_high; + double sample_period = 1.0 / sp->sampling_rate; + unsigned int samples_per_line; + double t; + unsigned int data; + unsigned int i; + + /* Twice 7 data + odd parity, start bit 0 -> 1 */ + + data = (sliced->data[1] << 12) + (sliced->data[0] << 4) + 8; + + t = sp->offset / (double) sp->sampling_rate; + + samples_per_line = SAMPLES_PER_LINE (sp); + + if (flags & _VBI_RAW_SHIFT_CC_CRI) { + /* Wrong signal shape found by Rich Kadel, + zapping-misc@lists.sourceforge.net 2006-07-16. */ + t0 += D / 2; + t1 += D / 2; + t2 += D / 2; + } + + if (flags & _VBI_RAW_LOW_AMP_CC) { + /* Low amplitude signal found by Rich Kadel, + zapping-misc@lists.sourceforge.net 2007-08-15. */ + white_level = white_level * 6 / 10; + } + + signal_mean = (white_level - blank_level) * .25; /* 25 IRE */ + signal_high = blank_level + (white_level - blank_level) * .5; + + for (i = 0; i < samples_per_line; ++i) { + if (t >= t1 && t < t2) { + raw[i] = SATURATE (blank_level + (1.0 - cos (q1 * (t - t1))) + * signal_mean, 0, 255); + } else { + unsigned int bit; + unsigned int seq; + double d; + + d = t - t3; + bit = d * bit_rate; + seq = (data >> bit) & 3; + + d -= bit * D; + if ((1 == seq || 2 == seq) + && fabs (d) < .120e-6) { + int level; + + if (1 == seq) + level = blank_level + (1.0 + cos (q2 * d)) + * signal_mean; + else + level = blank_level + (1.0 - cos (q2 * d)) + * signal_mean; + raw[i] = SATURATE (level, 0, 255); + } else if (data & (2 << bit)) { + raw[i] = SATURATE (signal_high, 0, 255); + } else { + raw[i] = SATURATE (blank_level, 0, 255); + } + } + + t += sample_period; + } +} + +static void +clear_image (uint8_t * p, + unsigned int value, + unsigned int width, unsigned int height, unsigned int bytes_per_line) +{ + if (width == bytes_per_line) { + memset (p, value, height * bytes_per_line); + } else { + while (height-- > 0) { + memset (p, value, width); + p += bytes_per_line; + } + } +} + +/** + * @param raw Noise will be added to this raw VBI image. + * @param sp Describes the raw VBI data in the buffer. @a sp->sampling_format + * must be @c VBI_PIXFMT_Y8 (@c VBI_PIXFMT_YUV420 in libzvbi 0.2.x). + * Note for compatibility in libzvbi 0.2.x vbi_sampling_par is a + * synonym of vbi_raw_decoder, but the (private) decoder fields in + * this structure are ignored. + * @param min_freq Minimum frequency of the noise in Hz. + * @param max_freq Maximum frequency of the noise in Hz. @a min_freq and + * @a max_freq define the cut off frequency at the half power points + * (gain -3 dB). + * @param amplitude Maximum amplitude of the noise, should lie in range + * 0 to 256. + * @param seed Seed for the pseudo random number generator built into + * this function. Given the same @a seed value the function will add + * the same noise, which can be useful for tests. + * + * This function adds white noise to a raw VBI image. + * + * To produce realistic noise @a min_freq = 0, @a max_freq = 5e6 and + * @a amplitude = 20 to 50 seems appropriate. + * + * @returns + * FALSE if the @a sp sampling parameters are invalid. + * + * @since 0.2.26 + */ +vbi_bool +vbi_raw_add_noise (uint8_t * raw, + const vbi_sampling_par * sp, + unsigned int min_freq, + unsigned int max_freq, unsigned int amplitude, unsigned int seed) +{ + double f0, w0, sn, cs, bw, alpha, a0; + float a1, a2, b0, b1, z0, z1, z2; + unsigned int n_lines; + unsigned long samples_per_line; + unsigned long padding; + uint32_t seed32; + + assert (NULL != raw); + assert (NULL != sp); + + if (unlikely (!_vbi_sampling_par_valid_log (sp, /* log */ NULL))) + return FALSE; + + switch (sp->sp_sample_format) { + case VBI_PIXFMT_YUV420: + break; + + default: + return FALSE; + } + + if (unlikely (sp->sampling_rate <= 0)) + return FALSE; + + /* Biquad bandpass filter. + http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */ + + f0 = ((double) min_freq + max_freq) * 0.5; + + if (f0 <= 0.0) + return TRUE; + + w0 = 2 * M_PI * f0 / sp->sampling_rate; + vbi_sincos (w0, &sn, &cs); + bw = fabs (vbi_log2 (MAX (min_freq, max_freq) / f0)); + alpha = sn * sinh (log (2) / 2 * bw * w0 / sn); + a0 = 1 + alpha; + a1 = 2 * cs / a0; + a2 = (alpha - 1) / a0; + b0 = sn / (2 * a0); + b1 = 0; + + if (amplitude > 256) + amplitude = 256; + + n_lines = sp->count[0] + sp->count[1]; + + if (unlikely (0 == amplitude || 0 == n_lines || 0 == sp->bytes_per_line)) + return TRUE; + + samples_per_line = sp->bytes_per_line; + padding = 0; + + seed32 = seed; + + z1 = 0; + z2 = 0; + + do { + uint8_t *raw_end = raw + samples_per_line; + + do { + int noise; + + /* We use our own simple PRNG to produce + predictable results for tests. */ + seed32 = seed32 * 1103515245u + 12345; + noise = ((seed32 / 65536) % (amplitude * 2 + 1)) + - amplitude; + + z0 = noise + a1 * z1 + a2 * z2; + noise = (int) (b0 * (z0 - z2) + b1 * z1); + z2 = z1; + z1 = z0; + + *raw++ = SATURATE (*raw + noise, 0, 255); + } while (raw < raw_end); + + raw += padding; + } while (--n_lines > 0); + + return TRUE; +} + +static vbi_bool +signal_u8 (uint8_t * raw, + const vbi_sampling_par * sp, + int blank_level, + int black_level, + int white_level, + unsigned int flags, + const vbi_sliced * sliced, unsigned int n_sliced_lines, const char *caller) +{ + unsigned int n_scan_lines; + unsigned int samples_per_line; + + n_scan_lines = sp->count[0] + sp->count[1]; + samples_per_line = SAMPLES_PER_LINE (sp); + + clear_image (raw, + SATURATE (blank_level, 0, 255), + samples_per_line, n_scan_lines, sp->bytes_per_line); + + for (; n_sliced_lines-- > 0; ++sliced) { + unsigned int row; + uint8_t *raw1; + + if (0 == sliced->line) { + goto bounds; + } else if (0 != sp->start[1] + && sliced->line >= (unsigned int) sp->start[1]) { + row = sliced->line - sp->start[1]; + if (row >= (unsigned int) sp->count[1]) + goto bounds; + + if (sp->interlaced) { + row = row * 2 + !(flags & _VBI_RAW_SWAP_FIELDS); + } else if (0 == (flags & _VBI_RAW_SWAP_FIELDS)) { + row += sp->count[0]; + } + } else if (0 != sp->start[0] + && sliced->line >= (unsigned int) sp->start[0]) { + row = sliced->line - sp->start[0]; + if (row >= (unsigned int) sp->count[0]) + goto bounds; + + if (sp->interlaced) { + row *= 2 + ! !(flags & _VBI_RAW_SWAP_FIELDS); + } else if (flags & _VBI_RAW_SWAP_FIELDS) { + row += sp->count[0]; + } + } else { + bounds: + warning (caller, "Sliced line %u out of bounds.", sliced->line); + return FALSE; + } + + raw1 = raw + row * sp->bytes_per_line; + + switch (sliced->id) { + case VBI_SLICED_TELETEXT_A: /* ok? */ + signal_teletext (raw1, sp, black_level, + /* amplitude */ .7 * (white_level + - black_level), + /* bit_rate */ 25 * 625 * 397, + /* FRC */ 0xE7, + /* payload */ 37, + sliced); + break; + + case VBI_SLICED_TELETEXT_B_L10_625: + case VBI_SLICED_TELETEXT_B_L25_625: + case VBI_SLICED_TELETEXT_B: + signal_teletext (raw1, sp, black_level, + .66 * (white_level - black_level), + 25 * 625 * 444, 0x27, 42, sliced); + break; + + case VBI_SLICED_TELETEXT_C_625: + signal_teletext (raw1, sp, black_level, + .7 * (white_level - black_level), 25 * 625 * 367, 0xE7, 33, sliced); + break; + + case VBI_SLICED_TELETEXT_D_625: + signal_teletext (raw1, sp, black_level, + .7 * (white_level - black_level), 5642787, 0xA7, 34, sliced); + break; + + case VBI_SLICED_CAPTION_625_F1: + case VBI_SLICED_CAPTION_625_F2: + case VBI_SLICED_CAPTION_625: + signal_closed_caption (raw1, sp, + blank_level, white_level, flags, 25 * 625 * 32, sliced); + break; + + case VBI_SLICED_VPS: + case VBI_SLICED_VPS_F2: + signal_vps (raw1, sp, black_level, white_level, sliced); + break; + + case VBI_SLICED_WSS_625: + signal_wss_625 (raw1, sp, black_level, white_level, sliced); + break; + + case VBI_SLICED_TELETEXT_B_525: + signal_teletext (raw1, sp, black_level, + /* amplitude */ .7 * (white_level + - black_level), + /* bit_rate */ 5727272, + /* FRC */ 0x27, + /* payload */ 34, + sliced); + break; + + case VBI_SLICED_TELETEXT_C_525: + signal_teletext (raw1, sp, black_level, + .7 * (white_level - black_level), 5727272, 0xE7, 33, sliced); + break; + + case VBI_SLICED_TELETEXT_D_525: + signal_teletext (raw1, sp, black_level, + .7 * (white_level - black_level), 5727272, 0xA7, 34, sliced); + break; + + case VBI_SLICED_CAPTION_525_F1: + case VBI_SLICED_CAPTION_525_F2: + case VBI_SLICED_CAPTION_525: + signal_closed_caption (raw1, sp, + blank_level, white_level, flags, 30000 * 525 * 32 / 1001, sliced); + break; + + default: + warning (caller, + "Service 0x%08x (%s) not supported.", + sliced->id, vbi_sliced_name (sliced->id)); + return FALSE; + } + } + + return TRUE; +} + +vbi_bool +_vbi_raw_vbi_image (uint8_t * raw, + unsigned long raw_size, + const vbi_sampling_par * sp, + int blank_level, + int white_level, + unsigned int flags, const vbi_sliced * sliced, unsigned int n_sliced_lines) +{ + unsigned int n_scan_lines; + unsigned int black_level; + + if (unlikely (!_vbi_sampling_par_valid_log (sp, NULL))) + return FALSE; + + n_scan_lines = sp->count[0] + sp->count[1]; + if (unlikely (n_scan_lines * sp->bytes_per_line > raw_size)) { + warning (__FUNCTION__, + "(%u + %u lines) * %lu bytes_per_line " + "> %lu raw_size.", + sp->count[0], sp->count[1], + (unsigned long) sp->bytes_per_line, raw_size); + return FALSE; + } + + if (unlikely (0 != white_level && blank_level > white_level)) { + warning (__FUNCTION__, + "Invalid blanking %d or peak white level %d.", + blank_level, white_level); + } + + if (SYSTEM_525 (sp)) { + /* Observed value. */ + const unsigned int peak = 200; /* 255 */ + + if (0 == white_level) { + blank_level = (int) (40.0 * peak / 140); + black_level = (int) (47.5 * peak / 140); + white_level = peak; + } else { + black_level = (int) (blank_level + 7.5 * (white_level - blank_level)); + } + } else { + const unsigned int peak = 200; /* 255 */ + + if (0 == white_level) { + blank_level = (int) (43.0 * peak / 140); + white_level = peak; + } + + black_level = blank_level; + } + + return signal_u8 (raw, sp, + blank_level, black_level, white_level, + flags, sliced, n_sliced_lines, __FUNCTION__); +} + +#define RGBA_TO_RGB16(value) \ + (+(((value) & 0xF8) >> (3 - 0)) \ + +(((value) & 0xFC00) >> (10 - 5)) \ + +(((value) & 0xF80000) >> (19 - 11))) + +#define RGBA_TO_RGBA15(value) \ + (+(((value) & 0xF8) >> (3 - 0)) \ + +(((value) & 0xF800) >> (11 - 5)) \ + +(((value) & 0xF80000) >> (19 - 10)) \ + +(((value) & 0x80000000) >> (31 - 15))) + +#define RGBA_TO_ARGB15(value) \ + (+(((value) & 0xF8) >> (3 - 1)) \ + +(((value) & 0xF800) >> (11 - 6)) \ + +(((value) & 0xF80000) >> (19 - 11)) \ + +(((value) & 0x80000000) >> (31 - 0))) + +#define RGBA_TO_RGBA12(value) \ + (+(((value) & 0xF0) >> (4 - 0)) \ + +(((value) & 0xF000) >> (12 - 4)) \ + +(((value) & 0xF00000) >> (20 - 8)) \ + +(((value) & 0xF0000000) >> (28 - 12))) + +#define RGBA_TO_ARGB12(value) \ + (+(((value) & 0xF0) << -(4 - 12)) \ + +(((value) & 0xF000) >> (12 - 8)) \ + +(((value) & 0xF00000) >> (20 - 4)) \ + +(((value) & 0xF0000000) >> (28 - 0))) + +#define RGBA_TO_RGB8(value) \ + (+(((value) & 0xE0) >> (5 - 0)) \ + +(((value) & 0xE000) >> (13 - 3)) \ + +(((value) & 0xC00000) >> (22 - 6))) + +#define RGBA_TO_BGR8(value) \ + (+(((value) & 0xE0) >> (5 - 5)) \ + +(((value) & 0xE000) >> (13 - 2)) \ + +(((value) & 0xC00000) >> (22 - 0))) + +#define RGBA_TO_RGBA7(value) \ + (+(((value) & 0xC0) >> (6 - 0)) \ + +(((value) & 0xE000) >> (13 - 2)) \ + +(((value) & 0xC00000) >> (22 - 5)) \ + +(((value) & 0x80000000) >> (31 - 7))) + +#define RGBA_TO_ARGB7(value) \ + (+(((value) & 0xC0) >> (6 - 6)) \ + +(((value) & 0xE000) >> (13 - 3)) \ + +(((value) & 0xC00000) >> (22 - 1)) \ + +(((value) & 0x80000000) >> (31 - 0))) + +#define MST1(d, val, mask) (d) = ((d) & ~(mask)) | ((val) & (mask)) +#define MST2(d, val, mask) (d) = ((d) & (mask)) | (val) + +#define SCAN_LINE_TO_N(conv, n) \ +do { \ + for (i = 0; i < samples_per_line; ++i) { \ + uint8_t *dd = d + i * (n); \ + unsigned int value = s[i] * 0x01010101; \ + unsigned int mask = ~pixel_mask; \ + \ + value = conv (value) & pixel_mask; \ + MST2 (dd[0], value >> 0, mask >> 0); \ + if (n >= 2) \ + MST2 (dd[1], value >> 8, mask >> 8); \ + if (n >= 3) \ + MST2 (dd[2], value >> 16, mask >> 16); \ + if (n >= 4) \ + MST2 (dd[3], value >> 24, mask >> 24); \ + } \ +} while (0) + +#define SCAN_LINE_TO_RGB2(conv, endian) \ +do { \ + for (i = 0; i < samples_per_line; ++i) { \ + uint8_t *dd = d + i * 2; \ + unsigned int value = s[i] * 0x01010101; \ + unsigned int mask; \ + \ + value = conv (value) & pixel_mask; \ + mask = ~pixel_mask; \ + MST2 (dd[0 + endian], value >> 0, mask >> 0); \ + MST2 (dd[1 - endian], value >> 8, mask >> 8); \ + } \ +} while (0) + +vbi_bool +_vbi_raw_video_image (uint8_t * raw, + unsigned long raw_size, + const vbi_sampling_par * sp, + int blank_level, + int black_level, + int white_level, + unsigned int pixel_mask, + unsigned int flags, const vbi_sliced * sliced, unsigned int n_sliced_lines) +{ + unsigned int n_scan_lines; + unsigned int samples_per_line; + vbi_sampling_par sp8; + unsigned int size; + uint8_t *buf; + uint8_t *s; + uint8_t *d; + + if (unlikely (!_vbi_sampling_par_valid_log (sp, NULL))) + return FALSE; + + n_scan_lines = sp->count[0] + sp->count[1]; + if (unlikely (n_scan_lines * sp->bytes_per_line > raw_size)) { + warning (__FUNCTION__, + "%u + %u lines * %lu bytes_per_line > %lu raw_size.", + sp->count[0], sp->count[1], + (unsigned long) sp->bytes_per_line, raw_size); + return FALSE; + } + + if (unlikely (0 != white_level + && (blank_level > black_level || black_level > white_level))) { + warning (__FUNCTION__, + "Invalid blanking %d, black %d or peak " + "white level %d.", blank_level, black_level, white_level); + } + + switch (sp->sp_sample_format) { + case VBI_PIXFMT_YVYU: + case VBI_PIXFMT_VYUY: /* 0xAAUUVVYY */ + pixel_mask = (+((pixel_mask & 0xFF00) << 8) + + ((pixel_mask & 0xFF0000) >> 8) + + ((pixel_mask & 0xFF0000FF))); + break; + + case VBI_PIXFMT_RGBA24_BE: /* 0xRRGGBBAA */ + pixel_mask = SWAB32 (pixel_mask); + break; + + case VBI_PIXFMT_BGR24_LE: /* 0x00RRGGBB */ + case VBI_PIXFMT_BGRA15_LE: + case VBI_PIXFMT_BGRA15_BE: + case VBI_PIXFMT_ABGR15_LE: + case VBI_PIXFMT_ABGR15_BE: + pixel_mask = (+((pixel_mask & 0xFF) << 16) + + ((pixel_mask & 0xFF0000) >> 16) + + ((pixel_mask & 0xFF00FF00))); + break; + + case VBI_PIXFMT_BGRA24_BE: /* 0xBBGGRRAA */ + pixel_mask = (+((pixel_mask & 0xFFFFFF) << 8) + + ((pixel_mask & 0xFF000000) >> 24)); + break; + + default: + break; + } + + switch (sp->sp_sample_format) { + case VBI_PIXFMT_RGB16_LE: + case VBI_PIXFMT_RGB16_BE: + case VBI_PIXFMT_BGR16_LE: + case VBI_PIXFMT_BGR16_BE: + pixel_mask = RGBA_TO_RGB16 (pixel_mask); + break; + + case VBI_PIXFMT_RGBA15_LE: + case VBI_PIXFMT_RGBA15_BE: + case VBI_PIXFMT_BGRA15_LE: + case VBI_PIXFMT_BGRA15_BE: + pixel_mask = RGBA_TO_RGBA15 (pixel_mask); + break; + + case VBI_PIXFMT_ARGB15_LE: + case VBI_PIXFMT_ARGB15_BE: + case VBI_PIXFMT_ABGR15_LE: + case VBI_PIXFMT_ABGR15_BE: + pixel_mask = RGBA_TO_ARGB15 (pixel_mask); + break; + + default: + break; + } + + if (0 == pixel_mask) { + /* Done! :-) */ + return TRUE; + } + + /* ITU-R BT.601 sampling assumed. */ + + if (SYSTEM_525 (sp)) { + if (0 == white_level) { + /* Cutting off the bottom of the signal + confuses the vbi_bit_slicer (can't adjust + the threshold fast enough), probably other + decoders as well. */ + blank_level = 5; /* 16 - 40 * 220 / 100; */ + black_level = 16; + white_level = 16 + 219; + } + } else { + if (0 == white_level) { + /* Observed values: 30-30-280 (WSS PAL) -? */ + blank_level = 5; /* 16 - 43 * 220 / 100; */ + black_level = 16; + white_level = 16 + 219; + } + } + + sp8 = *sp; + + samples_per_line = SAMPLES_PER_LINE (sp); + + sp8.sampling_format = VBI_PIXFMT_YUV420; + + sp8.bytes_per_line = samples_per_line * 1 /* bpp */ ; + + size = n_scan_lines * samples_per_line; + buf = vbi_malloc (size); + if (NULL == buf) { + error (NULL, "Out of memory."); + errno = ENOMEM; + return FALSE; + } + + if (!signal_u8 (buf, &sp8, + blank_level, black_level, white_level, + flags, sliced, n_sliced_lines, __FUNCTION__)) { + vbi_free (buf); + return FALSE; + } + + s = buf; + d = raw; + + while (n_scan_lines-- > 0) { + unsigned int i; + + switch (sp->sp_sample_format) { + case VBI_PIXFMT_PAL8: + case VBI_PIXFMT_YUV420: + for (i = 0; i < samples_per_line; ++i) + MST1 (d[i], s[i], pixel_mask); + break; + + case VBI_PIXFMT_RGBA24_LE: + case VBI_PIXFMT_RGBA24_BE: + case VBI_PIXFMT_BGRA24_LE: + case VBI_PIXFMT_BGRA24_BE: + SCAN_LINE_TO_N (+, 4); + break; + + case VBI_PIXFMT_RGB24_LE: + case VBI_PIXFMT_BGR24_LE: + SCAN_LINE_TO_N (+, 3); + break; + + case VBI_PIXFMT_YUYV: + case VBI_PIXFMT_YVYU: + for (i = 0; i < samples_per_line; i += 2) { + uint8_t *dd = d + i * 2; + unsigned int uv = (s[i] + s[i + 1] + 1) >> 1; + + MST1 (dd[0], s[i], pixel_mask); + MST1 (dd[1], uv, pixel_mask >> 8); + MST1 (dd[2], s[i + 1], pixel_mask); + MST1 (dd[3], uv, pixel_mask >> 16); + } + break; + + case VBI_PIXFMT_UYVY: + case VBI_PIXFMT_VYUY: + for (i = 0; i < samples_per_line; i += 2) { + uint8_t *dd = d + i * 2; + unsigned int uv = (s[i] + s[i + 1] + 1) >> 1; + + MST1 (dd[0], uv, pixel_mask >> 8); + MST1 (dd[1], s[i], pixel_mask); + MST1 (dd[2], uv, pixel_mask >> 16); + MST1 (dd[3], s[i + 1], pixel_mask); + } + break; + + case VBI_PIXFMT_RGB16_LE: + case VBI_PIXFMT_BGR16_LE: + SCAN_LINE_TO_RGB2 (RGBA_TO_RGB16, 0); + break; + + case VBI_PIXFMT_RGB16_BE: + case VBI_PIXFMT_BGR16_BE: + SCAN_LINE_TO_RGB2 (RGBA_TO_RGB16, 1); + break; + + case VBI_PIXFMT_RGBA15_LE: + case VBI_PIXFMT_BGRA15_LE: + SCAN_LINE_TO_RGB2 (RGBA_TO_RGBA15, 0); + break; + + case VBI_PIXFMT_RGBA15_BE: + case VBI_PIXFMT_BGRA15_BE: + SCAN_LINE_TO_RGB2 (RGBA_TO_RGBA15, 1); + break; + + case VBI_PIXFMT_ARGB15_LE: + case VBI_PIXFMT_ABGR15_LE: + SCAN_LINE_TO_RGB2 (RGBA_TO_ARGB15, 0); + break; + + case VBI_PIXFMT_ARGB15_BE: + case VBI_PIXFMT_ABGR15_BE: + SCAN_LINE_TO_RGB2 (RGBA_TO_ARGB15, 1); + break; + + } + + s += sp8.bytes_per_line; + d += sp->bytes_per_line; + } + + vbi_free (buf); + + return TRUE; +} + +/** + * @example examples/rawout.c + * Raw VBI output example. + */ + +/** + * @param raw A raw VBI image will be stored here. + * @param raw_size Size of the @a raw buffer in bytes. The buffer + * must be large enough for @a sp->count[0] + count[1] lines + * of @a sp->bytes_per_line each, with @a sp->samples_per_line + * (in libzvbi 0.2.x @a sp->bytes_per_line) bytes actually written. + * @param sp Describes the raw VBI data to generate. @a sp->sampling_format + * must be @c VBI_PIXFMT_Y8 (@c VBI_PIXFMT_YUV420 with libzvbi 0.2.x). + * @a sp->synchronous is ignored. Note for compatibility in libzvbi + * 0.2.x vbi_sampling_par is a synonym of vbi_raw_decoder, but the + * (private) decoder fields in this structure are ignored. + * @param blank_level The level of the horizontal blanking in the raw + * VBI image. Must be <= @a white_level. + * @param white_level The peak white level in the raw VBI image. Set to + * zero to get the default blanking and white level. + * @param swap_fields If @c TRUE the second field will be stored first + * in the @c raw buffer. Note you can also get an interlaced image + * by setting @a sp->interlaced to @c TRUE. @a sp->synchronous is + * ignored. + * @param sliced Pointer to an array of vbi_sliced containing the + * VBI data to be encoded. + * @param n_sliced_lines Number of elements in the @a sliced array. + * + * This function basically reverses the operation of the vbi_raw_decoder, + * taking sliced VBI data and generating a raw VBI image similar to those + * you would get from raw VBI sampling hardware. The following data services + * are currently supported: All Teletext services, VPS, WSS 625, Closed + * Caption 525 and 625. + * + * The function encodes sliced data as is, e.g. without adding or + * checking parity bits, without checking if the line number is correct + * for the respective data service, or if the signal will fit completely + * in the given space (@a sp->offset and @a sp->samples_per_line at + * @a sp->sampling_rate). + * + * Apart of the payload the generated video signal is invariable and + * attempts to be faithful to related standards. You can only change the + * characteristics of the assumed capture device. Sync pulses and color + * bursts and not generated if the sampling parameters extend to this area. + * + * @note + * This function is mainly intended for testing purposes. It is optimized + * for accuracy, not for speed. + * + * @returns + * @c FALSE if the @a raw_size is too small, if the @a sp sampling + * parameters are invalid, if the signal levels are invalid, + * if the @a sliced array contains unsupported services or line numbers + * outside the @a sp sampling parameters. + * + * @since 0.2.22 + */ +vbi_bool +vbi_raw_vbi_image (uint8_t * raw, + unsigned long raw_size, + const vbi_sampling_par * sp, + int blank_level, + int white_level, + vbi_bool swap_fields, + const vbi_sliced * sliced, unsigned int n_sliced_lines) +{ + return _vbi_raw_vbi_image (raw, raw_size, sp, + blank_level, white_level, + swap_fields ? _VBI_RAW_SWAP_FIELDS : 0, sliced, n_sliced_lines); +} + +/** + * @param raw A raw VBI image will be stored here. + * @param raw_size Size of the @a raw buffer in bytes. The buffer + * must be large enough for @a sp->count[0] + count[1] lines + * of @a sp->bytes_per_line each, with @a sp->samples_per_line + * times bytes per pixel (in libzvbi 0.2.x @a sp->bytes_per_line) + * actually written. + * @param sp Describes the raw VBI data to generate. Note for + * compatibility in libzvbi 0.2.x vbi_sampling_par is a synonym of + * vbi_raw_decoder, but the (private) decoder fields in this + * structure are ignored. + * @param blank_level The level of the horizontal blanking in the raw + * VBI image. Must be <= @a black_level. + * @param black_level The black level in the raw VBI image. Must be + * <= @a white_level. + * @param white_level The peak white level in the raw VBI image. Set to + * zero to get the default blanking, black and white level. + * @param pixel_mask This mask selects which color or alpha channel + * shall contain VBI data. Depending on @a sp->sampling_format it is + * interpreted as 0xAABBGGRR or 0xAAVVUUYY. A value of 0x000000FF + * for example writes data in "red bits", not changing other + * bits in the @a raw buffer. When the @a sp->sampling_format is a + * planar YUV the function writes the Y plane only. + * @param swap_fields If @c TRUE the second field will be stored first + * in the @c raw buffer. Note you can also get an interlaced image + * by setting @a sp->interlaced to @c TRUE. @a sp->synchronous is + * ignored. + * @param sliced Pointer to an array of vbi_sliced containing the + * VBI data to be encoded. + * @param n_sliced_lines Number of elements in the @a sliced array. + * + * Generates a raw VBI image similar to those you get from video + * capture hardware. Otherwise identical to vbi_raw_vbi_image(). + * + * @returns + * @c FALSE if the @a raw_size is too small, if the @a sp sampling + * parameters are invalid, if the signal levels are invalid, + * if the @a sliced array contains unsupported services or line numbers + * outside the @a sp sampling parameters. + * + * @since 0.2.22 + */ +vbi_bool +vbi_raw_video_image (uint8_t * raw, + unsigned long raw_size, + const vbi_sampling_par * sp, + int blank_level, + int black_level, + int white_level, + unsigned int pixel_mask, + vbi_bool swap_fields, + const vbi_sliced * sliced, unsigned int n_sliced_lines) +{ + return _vbi_raw_video_image (raw, raw_size, sp, + blank_level, black_level, + white_level, pixel_mask, + swap_fields ? _VBI_RAW_SWAP_FIELDS : 0, sliced, n_sliced_lines); +} + +/* +Local variables: +c-set-style: K&R +c-basic-offset: 8 +End: +*/ diff --git a/ext/closedcaption/io-sim.h b/ext/closedcaption/io-sim.h new file mode 100644 index 0000000000..67ad9ccf8d --- /dev/null +++ b/ext/closedcaption/io-sim.h @@ -0,0 +1,104 @@ +/* + * libzvbi -- VBI device simulation + * + * Copyright (C) 2004, 2007 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: io-sim.h,v 1.11 2013-08-28 14:45:23 mschimek Exp $ */ + +#ifndef __ZVBI_IO_SIM_H__ +#define __ZVBI_IO_SIM_H__ + +#include "macros.h" +#include "sampling_par.h" + +VBI_BEGIN_DECLS + +/* Public */ + +/** + * @addtogroup Rawenc + * @{ + */ +extern vbi_bool +vbi_raw_video_image (uint8_t * raw, + unsigned long raw_size, + const vbi_sampling_par *sp, + int blank_level, + int black_level, + int white_level, + unsigned int pixel_mask, + vbi_bool swap_fields, + const vbi_sliced * sliced, + unsigned int n_sliced_lines); +extern vbi_bool +vbi_raw_add_noise (uint8_t * raw, + const vbi_sampling_par *sp, + unsigned int min_freq, + unsigned int max_freq, + unsigned int amplitude, + unsigned int seed); +extern vbi_bool +vbi_raw_vbi_image (uint8_t * raw, + unsigned long raw_size, + const vbi_sampling_par *sp, + int blank_level, + int white_level, + vbi_bool swap_fields, + const vbi_sliced * sliced, + unsigned int n_sliced_lines); + +#define _VBI_RAW_SWAP_FIELDS (1 << 0) +#define _VBI_RAW_SHIFT_CC_CRI (1 << 1) +#define _VBI_RAW_LOW_AMP_CC (1 << 2) + +/* NB. Currently this flag has no effect in _vbi_raw_*_image(). + Call vbi_raw_add_noise() instead. */ +#define _VBI_RAW_NOISE_2 (1 << 17) + +extern vbi_bool +_vbi_raw_video_image (uint8_t * raw, + unsigned long raw_size, + const vbi_sampling_par *sp, + int blank_level, + int black_level, + int white_level, + unsigned int pixel_mask, + unsigned int flags, + const vbi_sliced * sliced, + unsigned int n_sliced_lines); +extern vbi_bool +_vbi_raw_vbi_image (uint8_t * raw, + unsigned long raw_size, + const vbi_sampling_par *sp, + int blank_level, + int white_level, + unsigned int flags, + const vbi_sliced * sliced, + unsigned int n_sliced_lines); + +VBI_END_DECLS + +#endif /* __ZVBI_IO_SIM_H__ */ + +/* +Local variables: +c-set-style: K&R +c-basic-offset: 8 +End: +*/ diff --git a/ext/closedcaption/meson.build b/ext/closedcaption/meson.build index e76c71e805..c5c971b2e5 100644 --- a/ext/closedcaption/meson.build +++ b/ext/closedcaption/meson.build @@ -6,6 +6,7 @@ zvbi_sources = [ 'decoder.c', 'raw_decoder.c', 'sampling_par.c', + 'io-sim.c', ] if pangocairo_dep.found()