/*
 *  libzvbi - VBI device simulation
 *
 *  Copyright (C) 2004 Michael H. Schimek
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* $Id: io-sim.c,v 1.11 2006/09/24 03:08:41 mschimek Exp $ */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <math.h>		/* sin() */
#include <errno.h>
#include <ctype.h>		/* isspace() */
#include <limits.h>		/* INT_MAX */

#include "misc.h"
#include "sliced.h"
#include "version.h"
#include "sampling_par.h"
#include "raw_decoder.h"
#include "hamm.h"
#if 3 == VBI_VERSION_MINOR
#  include "vps.h"
#  include "wss.h"
#endif
#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.
 */

#if 2 == VBI_VERSION_MINOR
#  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
#endif

#undef warning
#define warning(function, templ, args...)				\
do {									\
	if (_vbi_global_log.mask & VBI_LOG_WARNING)			\
		_vbi_log_printf (_vbi_global_log.fn,			\
				  _vbi_global_log.user_data,		\
				  VBI_LOG_WARNING, function,		\
				  templ , ##args);			\
} while (0)

#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)

#if 3 == VBI_VERSION_MINOR
#  define SAMPLES_PER_LINE(sp) ((sp)->samples_per_line)
#else
#  define SAMPLES_PER_LINE(sp)						\
	((sp)->bytes_per_line / VBI_PIXFMT_BPP ((sp)->sampling_format))
#endif

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 = (white_level - blank_level) * .25; /* 25 IRE */
	double signal_high = blank_level + (white_level - blank_level) * .5;
	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 on
		   "channel 56 The History Channel". */
		t0 += D / 2;
		t1 += D / 2;
		t2 += D / 2;
	}

	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;
		}
	}
}

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 3 == VBI_VERSION_MINOR
	if (VBI_VIDEOSTD_SET_525_60 & sp->videostd_set) {
#else
	if (525 == sp->scanning) {
#endif
		/* 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->sampling_format) {
#if 3 == VBI_VERSION_MINOR
	case VBI_PIXFMT_YVUA24_LE:	/* 0xAAUUVVYY */
	case VBI_PIXFMT_YVU24_LE:	/* 0x00UUVVYY */
#endif
	case VBI_PIXFMT_YVYU:
	case VBI_PIXFMT_VYUY:		/* 0xAAUUVVYY */
		pixel_mask = (+ ((pixel_mask & 0xFF00) << 8)
			      + ((pixel_mask & 0xFF0000) >> 8)
			      + ((pixel_mask & 0xFF0000FF)));
		break;

#if 3 == VBI_VERSION_MINOR
	case VBI_PIXFMT_YUVA24_BE:	/* 0xYYUUVVAA */
#endif
	case VBI_PIXFMT_RGBA24_BE:	/* 0xRRGGBBAA */
		pixel_mask = SWAB32 (pixel_mask);
		break;

#if 3 == VBI_VERSION_MINOR
	case VBI_PIXFMT_YVUA24_BE:	/* 0xYYVVUUAA */
		pixel_mask = (+ ((pixel_mask & 0xFF) << 24)
			      + ((pixel_mask & 0xFFFF00))
			      + ((pixel_mask & 0xFF000000) >> 24));
		break;

	case VBI_PIXFMT_YUV24_BE:	/* 0xAAYYUUVV */
	case VBI_PIXFMT_ARGB24_BE:	/* 0xAARRGGBB */
	case VBI_PIXFMT_BGRA12_LE:
	case VBI_PIXFMT_BGRA12_BE:
	case VBI_PIXFMT_ABGR12_LE:
	case VBI_PIXFMT_ABGR12_BE:
	case VBI_PIXFMT_BGRA7:
	case VBI_PIXFMT_ABGR7:
#endif
	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;

#if 3 == VBI_VERSION_MINOR
	case VBI_PIXFMT_YVU24_BE:	/* 0x00YYVVUU */
		pixel_mask = (+ ((pixel_mask & 0xFF) << 16)
			      + ((pixel_mask & 0xFFFF00) >> 8));
		break;
#endif
	case VBI_PIXFMT_BGRA24_BE:	/* 0xBBGGRRAA */
		pixel_mask = (+ ((pixel_mask & 0xFFFFFF) << 8)
			      + ((pixel_mask & 0xFF000000) >> 24));
		break;

	default:
		break;
	}

	switch (sp->sampling_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;

#if 3 == VBI_VERSION_MINOR
	case VBI_PIXFMT_RGBA12_LE:
	case VBI_PIXFMT_RGBA12_BE:
	case VBI_PIXFMT_BGRA12_LE:
	case VBI_PIXFMT_BGRA12_BE:
		pixel_mask = RGBA_TO_RGBA12 (pixel_mask);
		break;

	case VBI_PIXFMT_ARGB12_LE:
	case VBI_PIXFMT_ARGB12_BE:
	case VBI_PIXFMT_ABGR12_LE:
	case VBI_PIXFMT_ABGR12_BE:
		pixel_mask = RGBA_TO_ARGB12 (pixel_mask);
		break;

	case VBI_PIXFMT_RGB8:
		pixel_mask = RGBA_TO_RGB8 (pixel_mask);
		break;

	case VBI_PIXFMT_BGR8:
		pixel_mask = RGBA_TO_BGR8 (pixel_mask);
		break;

	case VBI_PIXFMT_RGBA7:
	case VBI_PIXFMT_BGRA7:
		pixel_mask = RGBA_TO_RGBA7 (pixel_mask);
		break;

	case VBI_PIXFMT_ARGB7:
	case VBI_PIXFMT_ABGR7:
		pixel_mask = RGBA_TO_ARGB7 (pixel_mask);
		break;
#endif
	default:
		break;
	}

	if (0 == pixel_mask) {
		/* Done! :-) */
		return TRUE;
	}

	/* ITU-R BT.601 sampling assumed. */

#if 3 == VBI_VERSION_MINOR
	if (VBI_VIDEOSTD_SET_525_60 & sp->videostd_set) {
#else
	if (525 == sp->scanning) {
#endif
		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);

#if 3 == VBI_VERSION_MINOR
	sp8.sampling_format = VBI_PIXFMT_Y8;
#else
	sp8.sampling_format = VBI_PIXFMT_YUV420;
#endif

	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->sampling_format) {
#if 3 == VBI_VERSION_MINOR
		case VBI_PIXFMT_NONE:
		case VBI_PIXFMT_RESERVED0:
		case VBI_PIXFMT_RESERVED1:
		case VBI_PIXFMT_RESERVED2:
		case VBI_PIXFMT_RESERVED3:
			break;

		case VBI_PIXFMT_YUV444:
		case VBI_PIXFMT_YVU444:
		case VBI_PIXFMT_YUV422:
		case VBI_PIXFMT_YVU422:
		case VBI_PIXFMT_YUV411:
		case VBI_PIXFMT_YVU411:
		case VBI_PIXFMT_YVU420:
		case VBI_PIXFMT_YUV410:
		case VBI_PIXFMT_YVU410:
		case VBI_PIXFMT_Y8:
#endif
		case VBI_PIXFMT_YUV420:
			for (i = 0; i < samples_per_line; ++i)
				MST1 (d[i], s[i], pixel_mask);
			break;

#if 3 == VBI_VERSION_MINOR
		case VBI_PIXFMT_YUVA24_LE:
		case VBI_PIXFMT_YVUA24_LE:
		case VBI_PIXFMT_YUVA24_BE:
		case VBI_PIXFMT_YVUA24_BE:
#endif
		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;

#if 3 == VBI_VERSION_MINOR
		case VBI_PIXFMT_YUV24_LE:
		case VBI_PIXFMT_YUV24_BE:
		case VBI_PIXFMT_YVU24_LE:
		case VBI_PIXFMT_YVU24_BE:
#endif
		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;

#if 3 == VBI_VERSION_MINOR
		case VBI_PIXFMT_RGBA12_LE:
		case VBI_PIXFMT_BGRA12_LE:
			SCAN_LINE_TO_RGB2 (RGBA_TO_RGBA12, 0);
			break;

		case VBI_PIXFMT_RGBA12_BE:
		case VBI_PIXFMT_BGRA12_BE:
			SCAN_LINE_TO_RGB2 (RGBA_TO_RGBA12, 1);
			break;

		case VBI_PIXFMT_ARGB12_LE:
		case VBI_PIXFMT_ABGR12_LE:
			SCAN_LINE_TO_RGB2 (RGBA_TO_ARGB12, 0);
			break;

		case VBI_PIXFMT_ARGB12_BE:
		case VBI_PIXFMT_ABGR12_BE:
			SCAN_LINE_TO_RGB2 (RGBA_TO_ARGB12, 1);
			break;

		case VBI_PIXFMT_RGB8:
			SCAN_LINE_TO_N (RGBA_TO_RGB8, 1);
			break;

		case VBI_PIXFMT_BGR8:
			SCAN_LINE_TO_N (RGBA_TO_BGR8, 1);
			break;

		case VBI_PIXFMT_RGBA7:
		case VBI_PIXFMT_BGRA7:
			SCAN_LINE_TO_N (RGBA_TO_RGBA7, 1);
			break;

		case VBI_PIXFMT_ARGB7:
		case VBI_PIXFMT_ABGR7:
			SCAN_LINE_TO_N (RGBA_TO_ARGB7, 1);
			break;
#endif /* 3 == VBI_VERSION_MINOR */
		}

		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);
}

/*
	Capture interface
*/

#if 3 == VBI_VERSION_MINOR
#  include "io-priv.h"
#else
#  include "io.h"
#endif
#include "hamm.h"

#define MAGIC 0xd804289c

struct buffer {
	char *			data;
	unsigned int		size;
	unsigned int		capacity;
};

typedef struct {
	vbi_capture		cap;

	unsigned int		magic;

	vbi_sampling_par	sp;

	vbi3_raw_decoder *	rd;

	vbi_bool		decode_raw;

	vbi_capture_buffer	raw_buffer;
	size_t			raw_f1_size;
	size_t			raw_f2_size;

	uint8_t *		desync_buffer[2];
	unsigned int		desync_i;

	double			capture_time;
	int64_t			stream_time;

	vbi_capture_buffer	sliced_buffer;
	vbi_sliced		sliced[50];

	unsigned int		teletext_page;
	unsigned int		teletext_row;

	struct buffer		caption_buffers[2];
	unsigned int		caption_i;

	uint8_t			vps_buffer[13];

	uint8_t			wss_buffer[2];
} vbi_capture_sim;

static vbi_bool
extend_buffer			(struct buffer *	b,
				 unsigned int		new_capacity)
{
	char *new_data;

	new_data = vbi_realloc (b->data, new_capacity);
	if (NULL == new_data)
		return FALSE;

	b->data = new_data;
	b->capacity = new_capacity;

	return TRUE;
}

static const char
caption_default_test_stream [] =
	"<erase-displayed ch=\"0\"/><roll-up rows=\"4\"/><pac row=\"14\"/>"
	"LIBZVBI CAPTION SIMULATION CC1.<cr/>"
	"<erase-displayed ch=\"1\"/><roll-up rows=\"4\"/><pac row=\"14\"/>"
	"LIBZVBI CAPTION SIMULATION CC2.<cr/>"
	"<erase-displayed ch=\"2\"/><roll-up rows=\"4\"/><pac row=\"14\"/>"
	"LIBZVBI CAPTION SIMULATION CC3.<cr/>"
	"<erase-displayed ch=\"3\"/><roll-up rows=\"4\"/><pac row=\"14\"/>"
	"LIBZVBI CAPTION SIMULATION CC4.<cr/>"
;
	/* TODO: regression test for repeated control code bug:
	   <control code field 1>
	   <zero field 2>
	   <repeated control code field 1, to be ignored> */ 

static unsigned int
get_attr			(const char *		s,
				 const char *		name,
				 unsigned int		default_value,
				 unsigned int		minimum,
				 unsigned int		maximum)
{
	unsigned long value;
	unsigned int len;

	value = default_value;

	len = strlen (name);

	for (; 0 != *s && '>' != *s; ++s) {
		int delta;

		if (!isalpha (*s))
			continue;

		delta = strncmp (s, name, len);
		if (0 == delta) {
			s += len;
		} else {
			while (isalnum (*s))
				++s;
		}

		while (isspace (*s++))
			;

		if ('=' != s[-1] || '"' != *s)
			break;

		if (0 == delta) {
			value = strtoul (s + 1,
					 /* endp */ NULL,
					 /* base */ 0);
			break;
		}

		do ++s;
		while (0 != *s && '"' != *s);
	}

	value = SATURATE (value,
			  (unsigned long) minimum,
			  (unsigned long) maximum);

	return (unsigned int) value;
}

static vbi_bool
caption_append_zeroes		(vbi_capture_sim *	sim,
				 unsigned int		channel,
				 unsigned int		n_bytes)
{
	struct buffer *b;

	b = &sim->caption_buffers[(channel >> 1) & 1];

	if (b->size + n_bytes > b->capacity) {
		unsigned int new_capacity;

		new_capacity = b->capacity + ((n_bytes + 255) & ~255);
		if (!extend_buffer (b, new_capacity))
			return FALSE;
	}

	memset (b->data + b->size, 0x80, n_bytes);
	b->size += n_bytes;

	return TRUE;
}

static unsigned int
caption_append_command		(vbi_capture_sim *	sim,
				 unsigned int *		inout_ch,
				 const char *		s)
{
	static const _vbi_key_value_pair elements [] = {
		{ "aof", 0x1422 },
		{ "aon", 0x1423 },
		{ "backgr", 0x1020 },
		{ "backgr-transp", 0x172D },
		{ "bao", 0x102E },
		{ "bas", 0x102F },
		{ "bbo", 0x1024 },
		{ "bbs", 0x1025 },
		{ "bco", 0x1026 },
		{ "bcs", 0x1027 },
		{ "bgo", 0x1022 },
		{ "bgs", 0x1023 },
		{ "bmo", 0x102C },
		{ "bms", 0x102D },
		{ "bro", 0x1028 },
		{ "brs", 0x1029 },
		{ "bs", 0x1421 },
		{ "bt", 0x172D },
		{ "bwo", 0x1020 },
		{ "bws", 0x1021 },
		{ "byo", 0x102A },
		{ "bys", 0x102B },
		{ "cmd", 0x0001 },
		{ "cr", 0x142D },
		{ "delete-end-of-row", 0x1424 },
		{ "der", 0x1424 },
		{ "edm", 0x142C },
		{ "end-of-caption", 0x142F },
		{ "enm", 0x142E },
		{ "eoc", 0x142F },
		{ "erase-displayed", 0x142C },
		{ "erase-non-displayed", 0x142E },
		{ "extended2", 0x1200 },
		{ "extended3", 0x1300 },
		{ "fa", 0x172E },
		{ "fau", 0x172F },
		{ "flash-on", 0x1428 },
		{ "fon", 0x1428 },
		{ "foregr-black", 0x172E },
		{ "indent", 0x1050 },
		{ "mr", 0x1120 },
		{ "pac", 0x1040 },
		{ "pause", 0x0002 },
		{ "rcl", 0x1420 },
		{ "rdc", 0x1429 },
		{ "resume-caption", 0x1420 },
		{ "resume-direct", 0x1429 },
		{ "resume-text", 0x142B },
		{ "roll-up", 0x1425 },
		{ "rtd", 0x142B },
		{ "ru2", 0x1425 },
		{ "ru3", 0x1426 },
		{ "ru4", 0x1427 },
		{ "special", 0x1130 },
		{ "sync", 0x0003 },
		{ "tab", 0x1720 },
		{ "text-restart", 0x142A },
		{ "to1", 0x1721 },
		{ "to2", 0x1722 },
		{ "to3", 0x1723 },
		{ "tr", 0x142A },
	};
	static const int row_code [16] = {
		0x1140, 0x1160, 0x1240, 0x1260, 
		0x1540, 0x1560, 0x1640, 0x1660, 
		0x1740, 0x1760, 0x1040, 0x1340, 
		0x1360, 0x1440, 0x1460, -1
	};
	struct buffer *b;
	int value;
	unsigned int cmd;
	unsigned int n_frames;
	int n_padding_bytes;
	unsigned int row;
	unsigned int i;
	vbi_bool parity;

	if (!_vbi_keyword_lookup (&value, &s,
				   elements,
				   N_ELEMENTS (elements)))
		return TRUE;

	*inout_ch = get_attr (s, "ch", *inout_ch, 0, 3);

	cmd = value | ((*inout_ch & 1) << 11);

	parity = TRUE;

	switch (value) {
	case 1: /* cmd */
		cmd = get_attr (s, "code", 0, 0, 0xFFFF);
		parity = FALSE;
		break;

	case 2: /* pause */
		n_frames = get_attr (s, "frames", 60, 1, INT_MAX);
		if (n_frames > 120 * 60 * 30)
			return TRUE;

		return caption_append_zeroes (sim, *inout_ch, n_frames * 2);

	case 3: /* sync */
		n_padding_bytes = sim->caption_buffers[0].size
			- sim->caption_buffers[1].size;

		if (0 == n_padding_bytes)
			return TRUE;
		else if (n_padding_bytes < 0)
			return caption_append_zeroes (sim, 0, n_padding_bytes);
		else
			return caption_append_zeroes (sim, 2, n_padding_bytes);

	case 0x1020: /* backgr */
		cmd |= get_attr (s, "color", 0, 0, 7) << 1;
		cmd |= get_attr (s, "t", 0, 0, 1); /* transparent */
		break;

	case 0x1040: /* pac (preamble address code) */
		cmd |= row_code[get_attr (s, "row", 14, 0, 14)];
		cmd |= get_attr (s, "color", 0, 0, 7) << 1;
		cmd |= get_attr (s, "u", 0, 0, 1);
		break;

	case 0x1050: /* indent */
		cmd |= row_code[get_attr (s, "row", 14, 0, 14)];
		cmd |= (get_attr (s, "cols", 0, 0, 31) / 4) << 1;
		cmd |= get_attr (s, "u", 0, 0, 1);
		break;

	case 0x1120: /* mr (midrow code) */
		cmd |= get_attr (s, "color", 0, 0, 7) << 1;
		cmd |= get_attr (s, "u", 0, 0, 1);
		break;

	case 0x1130: /* special character */
		cmd |= get_attr (s, "code", 0, 0, 15);
		break;

	case 0x1200: /* extended character set */
	case 0x1300:
		cmd |= get_attr (s, "code", 32, 32, 63);
		break;

	case 0x1420: /* resume caption loading */
	case 0x1421: /* bs */
	case 0x1422: /* aof */
	case 0x1423: /* aon */
	case 0x1424: /* delete to end of row */
	case 0x1428: /* flash-on */
	case 0x1429: /* resume direct caption */
	case 0x142A: /* text restart */
	case 0x142B: /* resume text display */
	case 0x142C: /* erase displayed memory */
	case 0x142D: /* cr */
	case 0x142E: /* erase non-displayed memory */
	case 0x142F: /* end of caption */
		/* Field bit (EIA 608-B Sec. 8.4, 8.5). */
		cmd |= ((*inout_ch & 2) << 7);

	case 0x1425: /* roll_up */
	case 0x1426:
	case 0x1427:
		row = get_attr (s, "rows", 2, 2, 4);
		cmd += row - 2;
		cmd |= (*inout_ch & 2) << 7; /* field bit */
		break;

	case 0x1720: /* tab */
		cmd |= get_attr (s, "cols", 1, 1, 3);
		break;

	case 0x172E: /* foregr-black */
		cmd |= get_attr (s, "u", 0, 0, 1); /* underlined */
		break;

	default:
		break;
	}

	b = &sim->caption_buffers[(*inout_ch >> 1) & 1];
	i = b->size;

	if (i + 3 > b->capacity) {
		if (!extend_buffer (b, b->capacity + 256))
			return FALSE;
	}

	if (i & 1)
		b->data[i++] = 0x80;

	if (likely (parity)) {
		b->data[i] = vbi_par8 (cmd >> 8);
		b->data[i + 1] = vbi_par8 (cmd);
	} else {
		/* To test error checks. */
		b->data[i] = cmd >> 8;
		b->data[i + 1] = cmd;
	}

	b->size = i + 2;

	return TRUE;
}

#if 3 != VBI_VERSION_MINOR
static
#endif
vbi_bool
vbi_capture_sim_load_caption	(vbi_capture *		cap,
				 const char *		stream,
				 vbi_bool		append)
{
	vbi_capture_sim *sim;
	struct buffer *b;
	unsigned int ch;
	const char *s;

	assert (NULL != cap);

	sim = PARENT (cap, vbi_capture_sim, cap);
	assert (MAGIC == sim->magic);

	if (!append) {
		free (sim->caption_buffers[0].data);
		free (sim->caption_buffers[1].data);

		CLEAR (sim->caption_buffers);

		sim->caption_i = 0;
	}

	if (NULL == stream)
		return TRUE;

	ch = 0;

	b = &sim->caption_buffers[0];

	for (s = stream;;) {
		int c = *s++;

		if (0 == c) {
			break;
		} else if (c < 0x20) {
			continue;
		} else if ('&' == c) {
			if ('#' == *s) {
				char *end;

				c = strtoul (s + 1, &end, 10);
				s = end;

				if (';' == *s)
					++s;
			} else if (0 == strncmp (s, "amp;", 4)) {
				s += 4;
			} else if (0 == strncmp (s, "lt;", 3)) {
				s += 3;
				c = '<';
			} else if (0 == strncmp (s, "gt;", 3)) {
				s += 3;
				c = '>';
			}
		} else if ('<' == c) {
			int delimiter;

			if (!caption_append_command (sim, &ch, s))
				return FALSE;

			b = &sim->caption_buffers[(ch >> 1) & 1];

			/* Skip until '>', except between quotes. */
			delimiter = '>';
			for (; 0 != *s && delimiter != *s; ++s) {
				if ('"' == *s)
					delimiter ^= '>';
			}

			if (0 != *s)
				++s; /* skip delimiter */

			continue;
		}

		if (b->size >= b->capacity) {
			if (!extend_buffer (b, b->capacity + 256))
				return FALSE;
		}

		b->data[b->size++] = vbi_par8 (c);
	}

	return TRUE;
}

static void
gen_caption			(vbi_capture_sim *	sim,
				 vbi_sliced **		inout_sliced,
				 vbi_service_set	service_set,
				 unsigned int		line)
{
	vbi_sliced *s;
	struct buffer *b;
	unsigned int i;

	b = &sim->caption_buffers[(line > 200)];

	i = sim->caption_i;

	if (i + 1 < b->size) {
		s = *inout_sliced;
		*inout_sliced = s + 1;

		s->id = service_set;
		s->line = line;
		s->data[0] = b->data[i];
		s->data[1] = b->data[i + 1];
	}
}

static void
gen_teletext_b_row		(vbi_capture_sim *	sim,
				 uint8_t	 	return_buf[45])
{
	static uint8_t s1[2][10] = {
		{ 0x02, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15 },
		{ 0x02, 0x15, 0x02, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15 }
	};
	static uint8_t s2[32] = "100\2LIBZVBI\7            00:00:00";
	static uint8_t s3[40] = "  LIBZVBI TELETEXT SIMULATION           ";
	static uint8_t s4[40] = "  Page 100                              ";
	static uint8_t s5[10][42] = {
		{ 0x02, 0x2f, 0x97, 0x20, 0x37, 0x23, 0x23, 0x23, 0x23, 0x23,
		  0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
		  0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
		  0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
		  0xb5, 0x20 },
		{ 0xc7, 0x2f, 0x97, 0x0d, 0xb5, 0x04, 0x20, 0x9d, 0x83, 0x8c,
		  0x08, 0x2a, 0x2a, 0x2a, 0x89, 0x20, 0x20, 0x0d, 0x54, 0x45,
		  0xd3, 0x54, 0x20, 0xd0, 0xc1, 0xc7, 0x45, 0x8c, 0x20, 0x20,
		  0x08, 0x2a, 0x2a, 0x2a, 0x89, 0x0d, 0x20, 0x20, 0x1c, 0x97,
		  0xb5, 0x20 },
		{ 0x02, 0xd0, 0x97, 0x20, 0xb5, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0xea, 0x20 },
		{ 0xc7, 0xd0, 0x97, 0x20, 0xb5, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0xb5, 0x20 },
		{ 0x02, 0xc7, 0x97, 0x20, 0xb5, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x15, 0x1a, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
		  0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
		  0x2c, 0x2c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x97, 0x19,
		  0xb5, 0x20 },
		{ 0xc7, 0xc7, 0x97, 0x20, 0xb5, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
		  0xb5, 0x20 },
		{ 0x02, 0x8c, 0x97, 0x9e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x13,
		  0x7f, 0x7f, 0x7f, 0x7f, 0x16, 0x7f, 0x7f, 0x7f, 0x7f, 0x92,
		  0x7f, 0x92, 0x7f, 0x7f, 0x15, 0x7f, 0x7f, 0x15, 0x7f, 0x91,
		  0x91, 0x7f, 0x7f, 0x91, 0x94, 0x7f, 0x94, 0x7f, 0x94, 0x97,
		  0xb5, 0x20 },
		{ 0xc7, 0x8c, 0x97, 0x9e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x13,
		  0x7f, 0x7f, 0x7f, 0x7f, 0x16, 0x7f, 0x7f, 0x7f, 0x7f, 0x92,
		  0x7f, 0x7f, 0x7f, 0x7f, 0x15, 0x7f, 0x7f, 0x7f, 0x7f, 0x91,
		  0x7f, 0x7f, 0x7f, 0x7f, 0x94, 0x7f, 0x7f, 0x7f, 0x7f, 0x97,
		  0xb5, 0x20 },
		{ 0x02, 0x9b, 0x97, 0x9e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x13,
		  0x7f, 0x7f, 0x7f, 0x7f, 0x16, 0x7f, 0x7f, 0x7f, 0x7f, 0x92,
		  0x7f, 0x7f, 0x7f, 0x7f, 0x15, 0x7f, 0x7f, 0x7f, 0x7f, 0x91,
		  0x7f, 0x7f, 0x7f, 0x7f, 0x94, 0x7f, 0x7f, 0x7f, 0x7f, 0x97,
		  0xb5, 0x20 },
		{ 0xc7, 0x9b, 0x97, 0x20, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
		  0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
		  0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
		  0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
		  0xa1, 0x20 }
	};
	unsigned int i;

	return_buf[0] = 0x55;
	return_buf[1] = 0x55;
	return_buf[2] = 0x27;

	if (sim->teletext_row >= 13)
		sim->teletext_row = 0;

	switch (sim->teletext_row) {
	case 0:
		memcpy (return_buf + 3, s1[sim->teletext_page], 10);
		sim->teletext_page ^= 1;
		for (i = 0; i < 32; ++i)
			return_buf[13 + i] = vbi_par8 (s2[i]);
		break;

	case 1:
		return_buf[3] = 0x02;
		return_buf[4] = 0x02;
		for (i = 0; i < 40; ++i)
			return_buf[5 + i] = vbi_par8 (s3[i]);
		break;

	case 2:
		return_buf[3] = 0x02;
		return_buf[4] = 0x49;
		for (i = 0; i < 40; ++i)
			return_buf[5 + i] = vbi_par8 (s4[i]);
		break;

	default:
		memcpy (return_buf + 3, s5[sim->teletext_row - 3], 42);
		break;
	}

	++sim->teletext_row;
}

static void
gen_teletext_b			(vbi_capture_sim *	sim,
				 vbi_sliced **		inout_sliced,
				 vbi_sliced *		sliced_end,
				 unsigned int		line)
{
	uint8_t buf[45];
	vbi_sliced *s;

	s = *inout_sliced;

	if (s >= sliced_end)
		return;

	s->id = VBI_SLICED_TELETEXT_B;
	s->line = line;

	gen_teletext_b_row (sim, buf);
	memcpy (&s->data, buf + 3, 42);

	*inout_sliced = s + 1;
}

static unsigned int
gen_sliced_525			(vbi_capture_sim *	sim)
{
	vbi_sliced *s;
	unsigned int i;

	s = sim->sliced;

	assert (N_ELEMENTS (sim->sliced) >= 4);

	if (0) {
		for (i = 0; i < N_ELEMENTS (s->data); ++i)
			s->data[i] = rand ();

		s[1] = s[0];
		s[2] = s[0];

		s[0].id = VBI_SLICED_TELETEXT_B_525;
		s[0].line = 10;
		s[1].id = VBI_SLICED_TELETEXT_C_525;
		s[1].line = 11;
		s[2].id = VBI_SLICED_TELETEXT_D_525;
		s[2].line = 12;

		s += 3;
	}

	if (sim->caption_buffers[0].size > 0)
		gen_caption (sim, &s, VBI_SLICED_CAPTION_525, 21);

	if (sim->caption_buffers[1].size > 0)
		gen_caption (sim, &s, VBI_SLICED_CAPTION_525, 284);

	sim->caption_i += 2;
	if (sim->caption_i >= sim->caption_buffers[0].size
	    && sim->caption_i >= sim->caption_buffers[1].size)
		sim->caption_i = 0;

	return s - sim->sliced;
}

#if 3 == VBI_VERSION_MINOR

vbi_bool
vbi_capture_sim_load_vps	(vbi_capture *		cap,
				 const vbi_program_id *pid)
{
	vbi_capture_sim *sim;
	vbi_program_id pid2;

	assert (NULL != cap);

	sim = PARENT (cap, vbi_capture_sim, cap);
	assert (MAGIC == sim->magic);

	if (NULL == pid) {
		CLEAR (pid2);

		pid2.cni_type	= VBI_CNI_TYPE_VPS;
		pid2.channel	= VBI_PID_CHANNEL_VPS;
		pid2.pil	= VBI_PIL_TIMER_CONTROL;

		pid = &pid2;
	}

	return vbi_encode_vps_pdc (sim->vps_buffer, pid);
}

vbi_bool
vbi_capture_sim_load_wss_625	(vbi_capture *		cap,
				 const vbi_aspect_ratio *ar)
{
	vbi_capture_sim *sim;
	vbi_aspect_ratio ar2;

	assert (NULL != cap);

	sim = PARENT (cap, vbi_capture_sim, cap);
	assert (MAGIC == sim->magic);

	if (NULL == ar) {
		CLEAR (ar2);
		ar = &ar2;
	}

	return vbi_encode_wss_625 (sim->wss_buffer, ar);
}

#endif /* 3 == VBI_VERSION_MINOR */

static unsigned int
gen_sliced_625			(vbi_capture_sim *	sim)
{
	vbi_sliced *s;
	vbi_sliced *end;
	unsigned int i;

	s = sim->sliced;
	end = &sim->sliced[N_ELEMENTS (sim->sliced)];

	assert (N_ELEMENTS (sim->sliced) >= 5);

	if (0) {
		for (i = 0; i < N_ELEMENTS (s->data); ++i)
			s->data[i] = rand ();

		s[1] = s[0];
		s[2] = s[0];

		s[0].id = VBI_SLICED_TELETEXT_A;
		s[0].line = 6;
		s[1].id = VBI_SLICED_TELETEXT_C_625;
		s[1].line = 7;
		s[2].id = VBI_SLICED_TELETEXT_D_625;
		s[2].line = 8;
		
		s += 3;
	}

	gen_teletext_b (sim, &s, end - 3, 9);
	gen_teletext_b (sim, &s, end - 3, 10);
	gen_teletext_b (sim, &s, end - 3, 11);
	gen_teletext_b (sim, &s, end - 3, 12);
	gen_teletext_b (sim, &s, end - 3, 13);
	gen_teletext_b (sim, &s, end - 3, 14);
	gen_teletext_b (sim, &s, end - 3, 15);

	s->id = VBI_SLICED_VPS;
	s->line = 16;
	assert (sizeof (s->data) >= sizeof (sim->vps_buffer));
	memcpy (s->data, sim->vps_buffer, sizeof (sim->vps_buffer));
	++s;

	gen_teletext_b (sim, &s, end - 2, 19);
	gen_teletext_b (sim, &s, end - 2, 20);
	gen_teletext_b (sim, &s, end - 2, 21);

	if (sim->caption_buffers[0].size > 0)
		gen_caption (sim, &s, VBI_SLICED_CAPTION_625, 22);

	sim->caption_i += 2;
	if (sim->caption_i >= sim->caption_buffers[0].size)
		sim->caption_i = 0;

	s->id = VBI_SLICED_WSS_625;
	s->line = 23;
	assert (sizeof (s->data) >= sizeof (sim->wss_buffer));
	memcpy (s->data, sim->wss_buffer, sizeof (sim->wss_buffer));
	++s;

	gen_teletext_b (sim, &s, end, 320);
	gen_teletext_b (sim, &s, end, 321);
	gen_teletext_b (sim, &s, end, 322);
	gen_teletext_b (sim, &s, end, 323);
	gen_teletext_b (sim, &s, end, 324);
	gen_teletext_b (sim, &s, end, 325);
	gen_teletext_b (sim, &s, end, 326);
	gen_teletext_b (sim, &s, end, 327);
	gen_teletext_b (sim, &s, end, 328);
	gen_teletext_b (sim, &s, end, 332);
	gen_teletext_b (sim, &s, end, 333);
	gen_teletext_b (sim, &s, end, 334);
	gen_teletext_b (sim, &s, end, 335);

	return s - sim->sliced;
}

/**
 * @param cap Initialized vbi_capture context opened with
 *   vbi_capture_sim_new().
 * @param enable @c TRUE to enable decoding of the simulated raw
 *   VBI data.
 *
 * By default this module generates sliced VBI data and converts it
 * to raw VBI data, returning both through the read functions. With
 * this function you can enable decoding of the raw VBI data back
 * to sliced VBI data, which is mainly interesting to test the
 * libzvbi bit slicer and raw VBI decoder.
 *
 * @since 0.2.22
 */
void
vbi_capture_sim_decode_raw	(vbi_capture *		cap,
				 vbi_bool		enable)
{
	vbi_capture_sim *sim;

	assert (NULL != cap);

	sim = PARENT (cap, vbi_capture_sim, cap);
	assert (MAGIC == sim->magic);

	sim->decode_raw = !!enable;
}

static void
copy_field			(uint8_t *		dst,
				 const uint8_t *	src,
				 unsigned int		height,
				 unsigned long		bytes_per_line)
{
	while (height-- > 0) {
		memcpy (dst, src, bytes_per_line);

		dst += bytes_per_line;
		src += bytes_per_line * 2;
	}
}

static void
delay_raw_data			(vbi_capture_sim *	sim,
				 uint8_t *		raw_data)
{
	unsigned int i;

	/* Delay the raw VBI data by one field. */

	i = sim->desync_i;

	if (sim->sp.interlaced) {
		assert (sim->sp.count[0] == sim->sp.count[1]);

		copy_field (sim->desync_buffer[i ^ 1],
			    raw_data + sim->sp.bytes_per_line,
			    sim->sp.count[0], sim->sp.bytes_per_line);
			    
		copy_field (raw_data + sim->sp.bytes_per_line,
			    raw_data,
			    sim->sp.count[0], sim->sp.bytes_per_line);

		copy_field (raw_data, sim->desync_buffer[i],
			    sim->sp.count[0], sim->sp.bytes_per_line);
	} else {
		memcpy (sim->desync_buffer[i ^ 1],
			raw_data + sim->raw_f1_size,
			sim->raw_f2_size);

		memmove (raw_data + sim->raw_f2_size,
			 raw_data,
			 sim->raw_f1_size);

		memcpy (raw_data,
			sim->desync_buffer[i],
			sim->raw_f2_size);
	}

	sim->desync_i = i ^ 1;
}

static vbi_bool
sim_read			(vbi_capture *		cap,
				 vbi_capture_buffer **	raw,
				 vbi_capture_buffer **	sliced,
				 const struct timeval *	timeout)
{
	vbi_capture_sim *sim = PARENT (cap, vbi_capture_sim, cap);
	unsigned int n_lines;

	timeout = timeout;

	n_lines = 0;

	if (NULL != raw
	    || NULL != sliced) {
#if 3 == VBI_VERSION_MINOR
		if (VBI_VIDEOSTD_SET_525_60 & sim->sp.videostd_set) {
#else
		if (525 == sim->sp.scanning) {
#endif
			n_lines = gen_sliced_525 (sim);
		} else {
			n_lines = gen_sliced_625 (sim);
		}
	}

	if (NULL != raw) {
		uint8_t *raw_data;
		vbi_bool success;

		if (NULL == *raw) {
			/* Return our buffer. */
			*raw = &sim->raw_buffer;
			raw_data = sim->raw_buffer.data;
		} else {
			/* XXX check max size here, after the API required
			   clients to pass one. */
			raw_data = (*raw)->data;
			(*raw)->size = sim->raw_buffer.size;
		}

		(*raw)->timestamp = sim->capture_time;

		memset (raw_data, 0x80, sim->raw_buffer.size);

		success = vbi_raw_vbi_image (raw_data,
					     sim->raw_buffer.size,
					     &sim->sp,
					     /* blank_level */ 0,
					     /* white_level */ 0,
					      /* swap_fields */ FALSE,
					     sim->sliced,
					     n_lines);
		assert (success);

		if (!sim->sp.synchronous)
			delay_raw_data (sim, raw_data);

		if (sim->decode_raw) {
			/* Decode the simulated raw VBI data to test our
			   encoder & decoder. */

			memset (sim->sliced, 0xAA, sizeof (sim->sliced));

			n_lines = vbi3_raw_decoder_decode
				(sim->rd,
				 sim->sliced, sizeof (sim->sliced),
				 raw_data);
		}
	}

	if (NULL != sliced) {
		if (NULL == *sliced) {
			/* Return our buffer. */
			*sliced = &sim->sliced_buffer;
		} else {
			/* XXX check max size here, after the API required
			   clients to pass one. */
			memcpy ((*sliced)->data, sim->sliced,
				n_lines * sizeof (sim->sliced[0]));
		}

		(*sliced)->size = n_lines * sizeof (sim->sliced[0]);
		(*sliced)->timestamp = sim->capture_time;
	}

#if 3 == VBI_VERSION_MINOR
	if (VBI_VIDEOSTD_SET_525_60 & sim->sp.videostd_set) {
#else
	if (525 == sim->sp.scanning) {
#endif
		sim->capture_time += 1001 / 30000.0;
	} else {
		sim->capture_time += 1 / 25.0;
	}

	return TRUE;
}

static vbi_raw_decoder *
sim_parameters			(vbi_capture *		cap)
{
	vbi_capture_sim *sim = PARENT (cap, vbi_capture_sim, cap);

	/* For compatibility in libzvbi 0.2
	   struct vbi_sampling_par == vbi_raw_decoder. In 0.3
	   we'll drop the decoding related fields. */
#if 3 == VBI_VERSION_MINOR
	return &sim->rd;
#else
	return &sim->sp;
#endif
}

static int
sim_get_fd			(vbi_capture *		cap)
{
	cap = cap;

	return -1; /* not available */
}

static void
sim_delete			(vbi_capture *		cap)
{
	vbi_capture_sim *sim = PARENT (cap, vbi_capture_sim, cap);

	vbi_capture_sim_load_caption (cap,
				       /* test_stream */ NULL,
				       /* append */ FALSE);

	vbi3_raw_decoder_delete (sim->rd);

	free (sim->desync_buffer[1]);
	free (sim->desync_buffer[0]);

	free (sim->raw_buffer.data);

	CLEAR (*sim);

	free (sim);
}

/**
 * @param scanning Whether to simulate a device receiving PAL/SECAM
 *   (value 625) or NTSC (525) video.
 * @param services This parameter must point to a set of @ref VBI_SLICED_
 *   symbols describing the data services to be simulated. On return the
 *   services actually simulated will be stored here. Currently Teletext
 *   System B, VPS, PAL WSS and PAL/NTSC Closed Caption are supported.
 * @param interlaced If @c TRUE the simulated raw VBI images will be
 *   interlaced like video images. Otherwise they will contain fields in
 *   sequential order, the first field at the top. Usually real devices
 *   provide sequential images.
 * @param synchronous If @c FALSE raw VBI images will be delayed by
 *   one field (putting a bottom field first in raw VBI images), simulating
 *   defective hardware. The @a interlaced and @a synchronous parameters
 *   correspond to fields in struct vbi_raw_decoder.
 *
 * This function opens a simulated VBI device providing raw and sliced VBI
 * data. It can be used to test applications in absence of a real device.
 * 
 * The VBI data is valid but limited. Just one Teletext page and one line
 * of roll-up caption. The WSS and VPS data is set to defaults, the VPS
 * contains no CNI.
 *
 * @note
 * The simulation does not run in real time.
 * Reading from the simulated device will return data immediately.
 * 
 * @returns
 * Initialized vbi_capture context, @c NULL on failure (out of memory).
 *
 * @since 0.2.22
 */
vbi_capture *
vbi_capture_sim_new		(int			scanning,
				 unsigned int *		services,
				 vbi_bool		interlaced,
				 vbi_bool		synchronous)
{
	vbi_capture_sim *sim;
	vbi_videostd_set videostd_set;
	vbi_bool success;

	sim = calloc (1, sizeof (*sim));
	if (NULL == sim) {
		errno = ENOMEM;
		return NULL;
	}

	sim->magic = MAGIC;

	sim->cap.read		= sim_read;
	sim->cap.parameters	= sim_parameters;
	sim->cap.get_fd		= sim_get_fd;
	sim->cap._delete	= sim_delete;

	sim->capture_time = 0.0;

	videostd_set = _vbi_videostd_set_from_scanning (scanning);
	assert (VBI_VIDEOSTD_SET_EMPTY != videostd_set);

	/* Sampling parameters. */

	*services = vbi_sampling_par_from_services
		(&sim->sp, /* return max_rate */ NULL,
		 videostd_set, *services);
	if (0 == *services) {
		goto failure;
	}

	sim->sp.interlaced = interlaced;
	sim->sp.synchronous = synchronous;

	/* Raw VBI buffer. */

	sim->raw_f1_size = sim->sp.bytes_per_line * sim->sp.count[0];
	sim->raw_f2_size = sim->sp.bytes_per_line * sim->sp.count[1];

	sim->raw_buffer.size = sim->raw_f1_size + sim->raw_f2_size;
	sim->raw_buffer.data = malloc (sim->raw_buffer.size);
	if (NULL == sim->raw_buffer.data) {
		goto failure;
	}

	if (!synchronous) {
		size_t size;

		size = sim->sp.bytes_per_line * sim->sp.count[1];

		sim->desync_buffer[0] = calloc (1, size);
		sim->desync_buffer[1] = calloc (1, size);

		if (NULL == sim->desync_buffer[0]
		    || NULL == sim->desync_buffer[1]) {
			goto failure;
		}
	}

	/* Sliced VBI buffer. */

	sim->sliced_buffer.data = sim->sliced;
	sim->sliced_buffer.size = sizeof (sim->sliced);

	/* Raw VBI decoder. */

	sim->rd = vbi3_raw_decoder_new (&sim->sp);
	if (0 == sim->rd) {
		goto failure;
	}

	vbi3_raw_decoder_add_services (sim->rd, *services, 0);

	/* Signal simulation. */

#if 3 == VBI_VERSION_MINOR	
	success = vbi_capture_sim_load_vps (&sim->cap, NULL);
	assert (success);

	success = vbi_capture_sim_load_wss_625 (&sim->cap, NULL);
	assert (success);
#else
	{
		const char vps[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				     0x00, 0x00, 0x01, 0xff, 0xfc, 0x00,
				     0x00 };
		const char wss[] = { 0x08, 0x06 };

		memcpy (sim->vps_buffer, vps, sizeof (vps));
		memcpy (sim->wss_buffer, wss, sizeof (wss));
	}
#endif

	success = vbi_capture_sim_load_caption
		(&sim->cap, caption_default_test_stream,
		 /* append */ FALSE);
	if (!success) {
		goto failure;
	}

	return &sim->cap;

 failure:
	sim_delete (&sim->cap);

	return NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1