summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathieu Duponchelle <mathieu@centricular.com>2019-02-21 19:05:03 +0100
committerMathieu Duponchelle <mathieu@centricular.com>2019-03-06 10:32:43 +0100
commitfa8134ed1109e8dbc42a4fc74defdc668927fdaf (patch)
treebf05fceed8127118e3820c44c6611404782bcb53
parent67bb17e4fae76f8f662fdd69bba6cdaaf37a90fa (diff)
Check in vbi encoder
-rw-r--r--ext/closedcaption/hamm.h233
-rw-r--r--ext/closedcaption/io-sim.c1143
-rw-r--r--ext/closedcaption/io-sim.h104
-rw-r--r--ext/closedcaption/meson.build1
4 files changed, 1481 insertions, 0 deletions
diff --git a/ext/closedcaption/hamm.h b/ext/closedcaption/hamm.h
new file mode 100644
index 000000000..2fd8da700
--- /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 <inttypes.h> /* 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 000000000..8ba58ab22
--- /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 <math.h> /* sin(), log() */
+#include <errno.h>
+#include <ctype.h> /* isspace() */
+#include <limits.h> /* 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 000000000..67ad9ccf8
--- /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 e76c71e80..c5c971b2e 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()