/*
 *  Simple MPEG parser to achieve network/service information.
 *
 *  refered standards:
 *
 *    ETSI EN 300 468
 *    ETSI TR 101 211
 *    ETSI ETR 211
 *    ITU-T H.222.0
 *
 * 2005-05-10 - Basic ATSC PSIP parsing support added
 *    ATSC Standard Revision B (A65/B)
 *
 * Thanks to Sean Device from Triveni for providing access to ATSC signals
 *    and to Kevin Fowlks for his independent ATSC scanning tool.
 *
 * Please contribute: It is possible that some descriptors for ATSC are
 *        not parsed yet and thus the result won't be complete.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <assert.h>
#include <glob.h>
#include <ctype.h>
#include <iconv.h>
#include <langinfo.h>

#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>

#include "list.h"
#include "diseqc.h"
#include "dump-zap.h"
#include "dump-vdr.h"
#include "scan.h"
#include "lnb.h"

#include "atsc_psip_section.h"

static char demux_devname[80];

static struct dvb_frontend_info fe_info = {
	.type = -1
};

int verbosity = 2;

static int long_timeout;
static int current_tp_only;
static int get_other_nits;
static int vdr_dump_provider;
static int vdr_dump_channum;
static int no_ATSC_PSIP;
static int ATSC_type=1;
static int ca_select = -1;
static int serv_select = 7;
static int vdr_version = 3;
static struct lnb_types_st lnb_type;
static int unique_anon_services;

char *default_charset = "ISO-6937";
char *output_charset;
#define CS_OPTIONS "//TRANSLIT"

static enum fe_spectral_inversion spectral_inversion = INVERSION_AUTO;

enum table_type {
	PAT,
	PMT,
	SDT,
	NIT
};

enum format {
        OUTPUT_ZAP,
        OUTPUT_VDR,
	OUTPUT_PIDS
};
static enum format output_format = OUTPUT_ZAP;
static int output_format_set = 0;


enum polarisation {
	POLARISATION_HORIZONTAL     = 0x00,
	POLARISATION_VERTICAL       = 0x01,
	POLARISATION_CIRCULAR_LEFT  = 0x02,
	POLARISATION_CIRCULAR_RIGHT = 0x03
};

enum running_mode {
	RM_NOT_RUNNING = 0x01,
	RM_STARTS_SOON = 0x02,
	RM_PAUSING     = 0x03,
	RM_RUNNING     = 0x04
};

#define AUDIO_CHAN_MAX (32)
#define CA_SYSTEM_ID_MAX (16)

struct service {
	struct list_head list;
	int transport_stream_id;
	int service_id;
	char *provider_name;
	char *service_name;
	uint16_t pmt_pid;
	uint16_t pcr_pid;
	uint16_t video_pid;
	uint16_t audio_pid[AUDIO_CHAN_MAX];
	char audio_lang[AUDIO_CHAN_MAX][4];
	int audio_num;
	uint16_t ca_id[CA_SYSTEM_ID_MAX];
	int ca_num;
	uint16_t teletext_pid;
	uint16_t subtitling_pid;
	uint16_t ac3_pid;
	unsigned int type         : 8;
	unsigned int scrambled	  : 1;
	enum running_mode running;
	void *priv;
	int channel_num;
};

struct transponder {
	struct list_head list;
	struct list_head services;
	int network_id;
	int original_network_id;
	int transport_stream_id;
	enum fe_type type;
	struct dvb_frontend_parameters param;
	enum polarisation polarisation;		/* only for DVB-S */
	int orbital_pos;			/* only for DVB-S */
	unsigned int we_flag		  : 1;	/* West/East Flag - only for DVB-S */
	unsigned int scan_done		  : 1;
	unsigned int last_tuning_failed	  : 1;
	unsigned int other_frequency_flag : 1;	/* DVB-T */
	unsigned int wrong_frequency	  : 1;	/* DVB-T with other_frequency_flag */
	int n_other_f;
	uint32_t *other_f;			/* DVB-T freqeuency-list descriptor */
};


struct section_buf {
	struct list_head list;
	const char *dmx_devname;
	unsigned int run_once  : 1;
	unsigned int segmented : 1;	/* segmented by table_id_ext */
	int fd;
	int pid;
	int table_id;
	int table_id_ext;
	int section_version_number;
	uint8_t section_done[32];
	int sectionfilter_done;
	unsigned char buf[1024];
	time_t timeout;
	time_t start_time;
	time_t running_time;
	struct section_buf *next_seg;	/* this is used to handle
					 * segmented tables (like NIT-other)
					 */
};

static LIST_HEAD(scanned_transponders);
static LIST_HEAD(new_transponders);
static struct transponder *current_tp;


static void dump_dvb_parameters (FILE *f, struct transponder *p);

static void setup_filter (struct section_buf* s, const char *dmx_devname,
		          int pid, int tid, int tid_ext,
			  int run_once, int segmented, int timeout);
static void add_filter (struct section_buf *s);

static const char * fe_type2str(fe_type_t t);

/* According to the DVB standards, the combination of network_id and
 * transport_stream_id should be unique, but in real life the satellite
 * operators and broadcasters don't care enough to coordinate
 * the numbering. Thus we identify TPs by frequency (dvbscan handles only
 * one satellite at a time). Further complication: Different NITs on
 * one satellite sometimes list the same TP with slightly different
 * frequencies, so we have to search within some bandwidth.
 */
static struct transponder *alloc_transponder(uint32_t frequency)
{
	struct transponder *tp = calloc(1, sizeof(*tp));

	tp->param.frequency = frequency;
	INIT_LIST_HEAD(&tp->list);
	INIT_LIST_HEAD(&tp->services);
	list_add_tail(&tp->list, &new_transponders);
	return tp;
}

static int is_same_transponder(uint32_t f1, uint32_t f2)
{
	uint32_t diff;
	if (f1 == f2)
		return 1;
	diff = (f1 > f2) ? (f1 - f2) : (f2 - f1);
	//FIXME: use symbolrate etc. to estimate bandwidth
	if (diff < 2000) {
		debug("f1 = %u is same TP as f2 = %u\n", f1, f2);
		return 1;
	}
	return 0;
}

static struct transponder *find_transponder(uint32_t frequency)
{
	struct list_head *pos;
	struct transponder *tp;

	list_for_each(pos, &scanned_transponders) {
		tp = list_entry(pos, struct transponder, list);
		if (current_tp_only)
			return tp;
		if (is_same_transponder(tp->param.frequency, frequency))
			return tp;
	}
	list_for_each(pos, &new_transponders) {
		tp = list_entry(pos, struct transponder, list);
		if (is_same_transponder(tp->param.frequency, frequency))
			return tp;
	}
	return NULL;
}

static void copy_transponder(struct transponder *d, struct transponder *s)
{
	struct list_head *pos;
	struct service *service;

	if (d->transport_stream_id != s->transport_stream_id) {
		/* propagate change to any already allocated services */
		list_for_each(pos, &d->services) {
			service = list_entry(pos, struct service, list);
			service->transport_stream_id = s->transport_stream_id;
		}
	}

	d->network_id = s->network_id;
	d->original_network_id = s->original_network_id;
	d->transport_stream_id = s->transport_stream_id;
	d->type = s->type;
	memcpy(&d->param, &s->param, sizeof(d->param));
	d->polarisation = s->polarisation;
	d->orbital_pos = s->orbital_pos;
	d->we_flag = s->we_flag;
	d->scan_done = s->scan_done;
	d->last_tuning_failed = s->last_tuning_failed;
	d->other_frequency_flag = s->other_frequency_flag;
	d->n_other_f = s->n_other_f;
	if (d->n_other_f) {
		d->other_f = calloc(d->n_other_f, sizeof(uint32_t));
		memcpy(d->other_f, s->other_f, d->n_other_f * sizeof(uint32_t));
	}
	else
		d->other_f = NULL;
}

/* service_ids are guaranteed to be unique within one TP
 * (the DVB standards say theay should be unique within one
 * network, but in real life...)
 */
static struct service *alloc_service(struct transponder *tp, int service_id)
{
	struct service *s = calloc(1, sizeof(*s));
	INIT_LIST_HEAD(&s->list);
	s->service_id = service_id;
	s->transport_stream_id = tp->transport_stream_id;
	list_add_tail(&s->list, &tp->services);
	return s;
}

static struct service *find_service(struct transponder *tp, int service_id)
{
	struct list_head *pos;
	struct service *s;

	list_for_each(pos, &tp->services) {
		s = list_entry(pos, struct service, list);
		if (s->service_id == service_id)
			return s;
	}
	return NULL;
}


static void parse_ca_identifier_descriptor (const unsigned char *buf,
				     struct service *s)
{
	unsigned char len = buf [1];
	unsigned int i;

	buf += 2;

	if (len > sizeof(s->ca_id)) {
		len = sizeof(s->ca_id);
		warning("too many CA system ids\n");
	}
	memcpy(s->ca_id, buf, len);
	for (i = 0; i < len / sizeof(s->ca_id[0]); i++)
		moreverbose("  CA ID 0x%04x\n", s->ca_id[i]);
}


static void parse_iso639_language_descriptor (const unsigned char *buf, struct service *s)
{
	unsigned char len = buf [1];

	buf += 2;

	if (len >= 4) {
		debug("    LANG=%.3s %d\n", buf, buf[3]);
		memcpy(s->audio_lang[s->audio_num], buf, 3);
#if 0
		/* seems like the audio_type is wrong all over the place */
		//if (buf[3] == 0) -> normal
		if (buf[3] == 1)
			s->audio_lang[s->audio_num][3] = '!'; /* clean effects (no language) */
		else if (buf[3] == 2)
			s->audio_lang[s->audio_num][3] = '?'; /* for the hearing impaired */
		else if (buf[3] == 3)
			s->audio_lang[s->audio_num][3] = '+'; /* visually impaired commentary */
#endif
	}
}

static void parse_network_name_descriptor (const unsigned char *buf, void *dummy)
{
	(void)dummy;

	unsigned char len = buf [1];

	info("Network Name '%.*s'\n", len, buf + 2);
}

static void parse_terrestrial_uk_channel_number (const unsigned char *buf, void *dummy)
{
	(void)dummy;

	int i, n, channel_num, service_id;
	struct list_head *p1, *p2;
	struct transponder *t;
	struct service *s;

	// 32 bits per record
	n = buf[1] / 4;
	if (n < 1)
		return;

	// desc id, desc len, (service id, service number)
	buf += 2;
	for (i = 0; i < n; i++) {
		service_id = (buf[0]<<8)|(buf[1]&0xff);
		channel_num = ((buf[2]&0x03)<<8)|(buf[3]&0xff);
		debug("Service ID 0x%x has channel number %d ", service_id, channel_num);
		list_for_each(p1, &scanned_transponders) {
			t = list_entry(p1, struct transponder, list);
			list_for_each(p2, &t->services) {
				s = list_entry(p2, struct service, list);
				if (s->service_id == service_id)
					s->channel_num = channel_num;
			}
		}
		buf += 4;
	}
}


static long bcd32_to_cpu (const int b0, const int b1, const int b2, const int b3)
{
	return ((b0 >> 4) & 0x0f) * 10000000 + (b0 & 0x0f) * 1000000 +
	       ((b1 >> 4) & 0x0f) * 100000   + (b1 & 0x0f) * 10000 +
	       ((b2 >> 4) & 0x0f) * 1000     + (b2 & 0x0f) * 100 +
	       ((b3 >> 4) & 0x0f) * 10       + (b3 & 0x0f);
}


static const fe_code_rate_t fec_tab [8] = {
	FEC_AUTO, FEC_1_2, FEC_2_3, FEC_3_4,
	FEC_5_6, FEC_7_8, FEC_NONE, FEC_NONE
};


static const fe_modulation_t qam_tab [6] = {
	QAM_AUTO, QAM_16, QAM_32, QAM_64, QAM_128, QAM_256
};


static void parse_cable_delivery_system_descriptor (const unsigned char *buf,
					     struct transponder *t)
{
	if (!t) {
		warning("cable_delivery_system_descriptor outside transport stream definition (ignored)\n");
		return;
	}
	t->type = FE_QAM;

	t->param.frequency = bcd32_to_cpu (buf[2], buf[3], buf[4], buf[5]);
	t->param.frequency *= 100;
	t->param.u.qam.fec_inner = fec_tab[buf[12] & 0x07];
	t->param.u.qam.symbol_rate = 10 * bcd32_to_cpu (buf[9],
							buf[10],
							buf[11],
							buf[12] & 0xf0);
	if ((buf[8] & 0x0f) > 5)
		t->param.u.qam.modulation = QAM_AUTO;
	else
		t->param.u.qam.modulation = qam_tab[buf[8] & 0x0f];
	t->param.inversion = spectral_inversion;

	if (verbosity >= 5) {
		debug("%#04x/%#04x ", t->network_id, t->transport_stream_id);
		dump_dvb_parameters (stderr, t);
		if (t->scan_done)
			dprintf(5, " (done)");
		if (t->last_tuning_failed)
			dprintf(5, " (tuning failed)");
		dprintf(5, "\n");
	}
}

static void parse_satellite_delivery_system_descriptor (const unsigned char *buf,
						 struct transponder *t)
{
	if (!t) {
		warning("satellite_delivery_system_descriptor outside transport stream definition (ignored)\n");
		return;
	}
	t->type = FE_QPSK;
	t->param.frequency = 10 * bcd32_to_cpu (buf[2], buf[3], buf[4], buf[5]);
	t->param.u.qpsk.fec_inner = fec_tab[buf[12] & 0x07];
	t->param.u.qpsk.symbol_rate = 10 * bcd32_to_cpu (buf[9],
							 buf[10],
							 buf[11],
							 buf[12] & 0xf0);

	t->polarisation = (buf[8] >> 5) & 0x03;
	t->param.inversion = spectral_inversion;

	t->orbital_pos = bcd32_to_cpu (0x00, 0x00, buf[6], buf[7]);
	t->we_flag = buf[8] >> 7;

	if (verbosity >= 5) {
		debug("%#04x/%#04x ", t->network_id, t->transport_stream_id);
		dump_dvb_parameters (stderr, t);
		if (t->scan_done)
			dprintf(5, " (done)");
		if (t->last_tuning_failed)
			dprintf(5, " (tuning failed)");
		dprintf(5, "\n");
	}
}


static void parse_terrestrial_delivery_system_descriptor (const unsigned char *buf,
						   struct transponder *t)
{
	static const fe_modulation_t m_tab [] = { QPSK, QAM_16, QAM_64, QAM_AUTO };
	static const fe_code_rate_t ofec_tab [8] = { FEC_1_2, FEC_2_3, FEC_3_4,
					       FEC_5_6, FEC_7_8 };
	struct dvb_ofdm_parameters *o;

	if (!t) {
		warning("terrestrial_delivery_system_descriptor outside transport stream definition (ignored)\n");
		return;
	}
	o = &t->param.u.ofdm;
	t->type = FE_OFDM;

	t->param.frequency = (buf[2] << 24) | (buf[3] << 16);
	t->param.frequency |= (buf[4] << 8) | buf[5];
	t->param.frequency *= 10;
	t->param.inversion = spectral_inversion;

	o->bandwidth = BANDWIDTH_8_MHZ + ((buf[6] >> 5) & 0x3);
	o->constellation = m_tab[(buf[7] >> 6) & 0x3];
	o->hierarchy_information = HIERARCHY_NONE + ((buf[7] >> 3) & 0x3);

	if ((buf[7] & 0x7) > 4)
		o->code_rate_HP = FEC_AUTO;
	else
		o->code_rate_HP = ofec_tab [buf[7] & 0x7];

	if (((buf[8] >> 5) & 0x7) > 4)
		o->code_rate_LP = FEC_AUTO;
	else
		o->code_rate_LP = ofec_tab [(buf[8] >> 5) & 0x7];

	o->guard_interval = GUARD_INTERVAL_1_32 + ((buf[8] >> 3) & 0x3);

	o->transmission_mode = (buf[8] & 0x2) ?
			       TRANSMISSION_MODE_8K :
			       TRANSMISSION_MODE_2K;

	t->other_frequency_flag = (buf[8] & 0x01);

	if (verbosity >= 5) {
		debug("%#04x/%#04x ", t->network_id, t->transport_stream_id);
		dump_dvb_parameters (stderr, t);
		if (t->scan_done)
			dprintf(5, " (done)");
		if (t->last_tuning_failed)
			dprintf(5, " (tuning failed)");
		dprintf(5, "\n");
	}
}

static void parse_frequency_list_descriptor (const unsigned char *buf,
				      struct transponder *t)
{
	int n, i;
	typeof(*t->other_f) f;

	if (!t) {
		warning("frequency_list_descriptor outside transport stream definition (ignored)\n");
		return;
	}
	if (t->other_f)
		return;

	n = (buf[1] - 1) / 4;
	if (n < 1 || (buf[2] & 0x03) != 3)
		return;

	t->other_f = calloc(n, sizeof(*t->other_f));
	t->n_other_f = n;
	buf += 3;
	for (i = 0; i < n; i++) {
		f = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
		t->other_f[i] = f * 10;
		buf += 4;
	}
}

/*
 * handle character set correctly (e.g. via iconv)
 *   c.f. EN 300 468 annex A
 */

struct charset_conv {
	unsigned len;
	unsigned char  data[3];
};

/* This table is the Latin 00 table. Basically ISO-6937 + Euro sign */
struct charset_conv en300468_latin_00_to_utf8[256] = {
	[0x00] = { 1, {0x00, } },
	[0x01] = { 1, {0x01, } },
	[0x02] = { 1, {0x02, } },
	[0x03] = { 1, {0x03, } },
	[0x04] = { 1, {0x04, } },
	[0x05] = { 1, {0x05, } },
	[0x06] = { 1, {0x06, } },
	[0x07] = { 1, {0x07, } },
	[0x08] = { 1, {0x08, } },
	[0x09] = { 1, {0x09, } },
	[0x0a] = { 1, {0x0a, } },
	[0x0b] = { 1, {0x0b, } },
	[0x0c] = { 1, {0x0c, } },
	[0x0d] = { 1, {0x0d, } },
	[0x0e] = { 1, {0x0e, } },
	[0x0f] = { 1, {0x0f, } },
	[0x10] = { 1, {0x10, } },
	[0x11] = { 1, {0x11, } },
	[0x12] = { 1, {0x12, } },
	[0x13] = { 1, {0x13, } },
	[0x14] = { 1, {0x14, } },
	[0x15] = { 1, {0x15, } },
	[0x16] = { 1, {0x16, } },
	[0x17] = { 1, {0x17, } },
	[0x18] = { 1, {0x18, } },
	[0x19] = { 1, {0x19, } },
	[0x1a] = { 1, {0x1a, } },
	[0x1b] = { 1, {0x1b, } },
	[0x1c] = { 1, {0x1c, } },
	[0x1d] = { 1, {0x1d, } },
	[0x1e] = { 1, {0x1e, } },
	[0x1f] = { 1, {0x1f, } },
	[0x20] = { 1, {0x20, } },
	[0x21] = { 1, {0x21, } },
	[0x22] = { 1, {0x22, } },
	[0x23] = { 1, {0x23, } },
	[0x24] = { 1, {0x24, } },
	[0x25] = { 1, {0x25, } },
	[0x26] = { 1, {0x26, } },
	[0x27] = { 1, {0x27, } },
	[0x28] = { 1, {0x28, } },
	[0x29] = { 1, {0x29, } },
	[0x2a] = { 1, {0x2a, } },
	[0x2b] = { 1, {0x2b, } },
	[0x2c] = { 1, {0x2c, } },
	[0x2d] = { 1, {0x2d, } },
	[0x2e] = { 1, {0x2e, } },
	[0x2f] = { 1, {0x2f, } },
	[0x30] = { 1, {0x30, } },
	[0x31] = { 1, {0x31, } },
	[0x32] = { 1, {0x32, } },
	[0x33] = { 1, {0x33, } },
	[0x34] = { 1, {0x34, } },
	[0x35] = { 1, {0x35, } },
	[0x36] = { 1, {0x36, } },
	[0x37] = { 1, {0x37, } },
	[0x38] = { 1, {0x38, } },
	[0x39] = { 1, {0x39, } },
	[0x3a] = { 1, {0x3a, } },
	[0x3b] = { 1, {0x3b, } },
	[0x3c] = { 1, {0x3c, } },
	[0x3d] = { 1, {0x3d, } },
	[0x3e] = { 1, {0x3e, } },
	[0x3f] = { 1, {0x3f, } },
	[0x40] = { 1, {0x40, } },
	[0x41] = { 1, {0x41, } },
	[0x42] = { 1, {0x42, } },
	[0x43] = { 1, {0x43, } },
	[0x44] = { 1, {0x44, } },
	[0x45] = { 1, {0x45, } },
	[0x46] = { 1, {0x46, } },
	[0x47] = { 1, {0x47, } },
	[0x48] = { 1, {0x48, } },
	[0x49] = { 1, {0x49, } },
	[0x4a] = { 1, {0x4a, } },
	[0x4b] = { 1, {0x4b, } },
	[0x4c] = { 1, {0x4c, } },
	[0x4d] = { 1, {0x4d, } },
	[0x4e] = { 1, {0x4e, } },
	[0x4f] = { 1, {0x4f, } },
	[0x50] = { 1, {0x50, } },
	[0x51] = { 1, {0x51, } },
	[0x52] = { 1, {0x52, } },
	[0x53] = { 1, {0x53, } },
	[0x54] = { 1, {0x54, } },
	[0x55] = { 1, {0x55, } },
	[0x56] = { 1, {0x56, } },
	[0x57] = { 1, {0x57, } },
	[0x58] = { 1, {0x58, } },
	[0x59] = { 1, {0x59, } },
	[0x5a] = { 1, {0x5a, } },
	[0x5b] = { 1, {0x5b, } },
	[0x5c] = { 1, {0x5c, } },
	[0x5d] = { 1, {0x5d, } },
	[0x5e] = { 1, {0x5e, } },
	[0x5f] = { 1, {0x5f, } },
	[0x60] = { 1, {0x60, } },
	[0x61] = { 1, {0x61, } },
	[0x62] = { 1, {0x62, } },
	[0x63] = { 1, {0x63, } },
	[0x64] = { 1, {0x64, } },
	[0x65] = { 1, {0x65, } },
	[0x66] = { 1, {0x66, } },
	[0x67] = { 1, {0x67, } },
	[0x68] = { 1, {0x68, } },
	[0x69] = { 1, {0x69, } },
	[0x6a] = { 1, {0x6a, } },
	[0x6b] = { 1, {0x6b, } },
	[0x6c] = { 1, {0x6c, } },
	[0x6d] = { 1, {0x6d, } },
	[0x6e] = { 1, {0x6e, } },
	[0x6f] = { 1, {0x6f, } },
	[0x70] = { 1, {0x70, } },
	[0x71] = { 1, {0x71, } },
	[0x72] = { 1, {0x72, } },
	[0x73] = { 1, {0x73, } },
	[0x74] = { 1, {0x74, } },
	[0x75] = { 1, {0x75, } },
	[0x76] = { 1, {0x76, } },
	[0x77] = { 1, {0x77, } },
	[0x78] = { 1, {0x78, } },
	[0x79] = { 1, {0x79, } },
	[0x7a] = { 1, {0x7a, } },
	[0x7b] = { 1, {0x7b, } },
	[0x7c] = { 1, {0x7c, } },
	[0x7d] = { 1, {0x7d, } },
	[0x7e] = { 1, {0x7e, } },
	[0x7f] = { 1, {0x7f, } },
	[0x80] = { 2, {0xc2, 0x80, } },
	[0x81] = { 2, {0xc2, 0x81, } },
	[0x82] = { 2, {0xc2, 0x82, } },
	[0x83] = { 2, {0xc2, 0x83, } },
	[0x84] = { 2, {0xc2, 0x84, } },
	[0x85] = { 2, {0xc2, 0x85, } },
	[0x86] = { 2, {0xc2, 0x86, } },
	[0x87] = { 2, {0xc2, 0x87, } },
	[0x88] = { 2, {0xc2, 0x88, } },
	[0x89] = { 2, {0xc2, 0x89, } },
	[0x8a] = { 2, {0xc2, 0x8a, } },
	[0x8b] = { 2, {0xc2, 0x8b, } },
	[0x8c] = { 2, {0xc2, 0x8c, } },
	[0x8d] = { 2, {0xc2, 0x8d, } },
	[0x8e] = { 2, {0xc2, 0x8e, } },
	[0x8f] = { 2, {0xc2, 0x8f, } },
	[0x90] = { 2, {0xc2, 0x90, } },
	[0x91] = { 2, {0xc2, 0x91, } },
	[0x92] = { 2, {0xc2, 0x92, } },
	[0x93] = { 2, {0xc2, 0x93, } },
	[0x94] = { 2, {0xc2, 0x94, } },
	[0x95] = { 2, {0xc2, 0x95, } },
	[0x96] = { 2, {0xc2, 0x96, } },
	[0x97] = { 2, {0xc2, 0x97, } },
	[0x98] = { 2, {0xc2, 0x98, } },
	[0x99] = { 2, {0xc2, 0x99, } },
	[0x9a] = { 2, {0xc2, 0x9a, } },
	[0x9b] = { 2, {0xc2, 0x9b, } },
	[0x9c] = { 2, {0xc2, 0x9c, } },
	[0x9d] = { 2, {0xc2, 0x9d, } },
	[0x9e] = { 2, {0xc2, 0x9e, } },
	[0x9f] = { 2, {0xc2, 0x9f, } },
	[0xa0] = { 2, {0xc2, 0xa0, } },
	[0xa1] = { 2, {0xc2, 0xa1, } },
	[0xa2] = { 2, {0xc2, 0xa2, } },
	[0xa3] = { 2, {0xc2, 0xa3, } },
	[0xa4] = { 3, { 0xe2, 0x82, 0xac,} },		/* Euro sign. Addition over the ISO-6937 standard */
	[0xa5] = { 2, {0xc2, 0xa5, } },
	[0xa6] = { 0, {} },
	[0xa7] = { 2, {0xc2, 0xa7, } },
	[0xa8] = { 2, {0xc2, 0xa4, } },
	[0xa9] = { 3, {0xe2, 0x80, 0x98, } },
	[0xaa] = { 3, {0xe2, 0x80, 0x9c, } },
	[0xab] = { 2, {0xc2, 0xab, } },
	[0xac] = { 3, {0xe2, 0x86, 0x90, } },
	[0xad] = { 3, {0xe2, 0x86, 0x91, } },
	[0xae] = { 3, {0xe2, 0x86, 0x92, } },
	[0xaf] = { 3, {0xe2, 0x86, 0x93, } },
	[0xb0] = { 2, {0xc2, 0xb0, } },
	[0xb1] = { 2, {0xc2, 0xb1, } },
	[0xb2] = { 2, {0xc2, 0xb2, } },
	[0xb3] = { 2, {0xc2, 0xb3, } },
	[0xb4] = { 2, {0xc3, 0x97, } },
	[0xb5] = { 2, {0xc2, 0xb5, } },
	[0xb6] = { 2, {0xc2, 0xb6, } },
	[0xb7] = { 2, {0xc2, 0xb7, } },
	[0xb8] = { 2, {0xc3, 0xb7, } },
	[0xb9] = { 3, {0xe2, 0x80, 0x99, } },
	[0xba] = { 3, {0xe2, 0x80, 0x9d, } },
	[0xbb] = { 2, {0xc2, 0xbb, } },
	[0xbc] = { 2, {0xc2, 0xbc, } },
	[0xbd] = { 2, {0xc2, 0xbd, } },
	[0xbe] = { 2, {0xc2, 0xbe, } },
	[0xbf] = { 2, {0xc2, 0xbf, } },
	[0xc0] = { 0, {} },
	[0xc1] = { 0, {} },
	[0xc2] = { 0, {} },
	[0xc3] = { 0, {} },
	[0xc4] = { 0, {} },
	[0xc5] = { 0, {} },
	[0xc6] = { 0, {} },
	[0xc7] = { 0, {} },
	[0xc8] = { 0, {} },
	[0xc9] = { 0, {} },
	[0xca] = { 0, {} },
	[0xcb] = { 0, {} },
	[0xcc] = { 0, {} },
	[0xcd] = { 0, {} },
	[0xce] = { 0, {} },
	[0xcf] = { 0, {} },
	[0xd0] = { 3, {0xe2, 0x80, 0x94, } },
	[0xd1] = { 2, {0xc2, 0xb9, } },
	[0xd2] = { 2, {0xc2, 0xae, } },
	[0xd3] = { 2, {0xc2, 0xa9, } },
	[0xd4] = { 3, {0xe2, 0x84, 0xa2, } },
	[0xd5] = { 3, {0xe2, 0x99, 0xaa, } },
	[0xd6] = { 2, {0xc2, 0xac, } },
	[0xd7] = { 2, {0xc2, 0xa6, } },
	[0xd8] = { 0, {} },
	[0xd9] = { 0, {} },
	[0xda] = { 0, {} },
	[0xdb] = { 0, {} },
	[0xdc] = { 3, {0xe2, 0x85, 0x9b, } },
	[0xdd] = { 3, {0xe2, 0x85, 0x9c, } },
	[0xde] = { 3, {0xe2, 0x85, 0x9d, } },
	[0xdf] = { 3, {0xe2, 0x85, 0x9e, } },
	[0xe0] = { 3, {0xe2, 0x84, 0xa6, } },
	[0xe1] = { 2, {0xc3, 0x86, } },
	[0xe2] = { 2, {0xc3, 0x90, } },
	[0xe3] = { 2, {0xc2, 0xaa, } },
	[0xe4] = { 2, {0xc4, 0xa6, } },
	[0xe5] = { 0, {} },
	[0xe6] = { 2, {0xc4, 0xb2, } },
	[0xe7] = { 2, {0xc4, 0xbf, } },
	[0xe8] = { 2, {0xc5, 0x81, } },
	[0xe9] = { 2, {0xc3, 0x98, } },
	[0xea] = { 2, {0xc5, 0x92, } },
	[0xeb] = { 2, {0xc2, 0xba, } },
	[0xec] = { 2, {0xc3, 0x9e, } },
	[0xed] = { 2, {0xc5, 0xa6, } },
	[0xee] = { 2, {0xc5, 0x8a, } },
	[0xef] = { 2, {0xc5, 0x89, } },
	[0xf0] = { 2, {0xc4, 0xb8, } },
	[0xf1] = { 2, {0xc3, 0xa6, } },
	[0xf2] = { 2, {0xc4, 0x91, } },
	[0xf3] = { 2, {0xc3, 0xb0, } },
	[0xf4] = { 2, {0xc4, 0xa7, } },
	[0xf5] = { 2, {0xc4, 0xb1, } },
	[0xf6] = { 2, {0xc4, 0xb3, } },
	[0xf7] = { 2, {0xc5, 0x80, } },
	[0xf8] = { 2, {0xc5, 0x82, } },
	[0xf9] = { 2, {0xc3, 0xb8, } },
	[0xfa] = { 2, {0xc5, 0x93, } },
	[0xfb] = { 2, {0xc3, 0x9f, } },
	[0xfc] = { 2, {0xc3, 0xbe, } },
	[0xfd] = { 2, {0xc5, 0xa7, } },
	[0xfe] = { 2, {0xc5, 0x8b, } },
	[0xff] = { 2, {0xc2, 0xad, } },
};

static void descriptorcpy(char **dest, const unsigned char *src, size_t len)
{
	size_t destlen, i;
	char *p, *type = default_charset;
	unsigned char *tmp = NULL;
	const unsigned char *s;
	int emphasis = 0, need_conversion = 1;

	if (*dest) {
		free (*dest);
		*dest = NULL;
	}
	if (!len)
		return;

	if (*src < 0x20) {
		switch (*src) {
		case 0x00:	type = "ISO-6937";		break;
		case 0x01:	type = "ISO-8859-5";		break;
		case 0x02:	type = "ISO-8859-6";		break;
		case 0x03:	type = "ISO-8859-7";		break;
		case 0x04:	type = "ISO-8859-8";		break;
		case 0x05:	type = "ISO-8859-9";		break;
		case 0x06:	type = "ISO-8859-10";		break;
		case 0x07:	type = "ISO-8859-11";		break;
		case 0x09:	type = "ISO-8859-13";		break;
		case 0x0a:	type = "ISO-8859-14";		break;
		case 0x0b:	type = "ISO-8859-15";		break;
		case 0x11:	type = "ISO-10646";		break;
		case 0x12:	type = "ISO-2022-KR";		break;
		case 0x13:	type = "GB2312";		break;
		case 0x14:	type = "BIG5";			break;
		case 0x15:	type = "ISO-10646/UTF-8";	break;
		case 0x10: /* ISO8859 */
			if ((*(src + 1) != 0) || *(src + 2) > 0x0f)
				break;
			src+=2;
			len-=2;
			switch(*src) {
			case 0x01:	type = "ISO-8859-1";		break;
			case 0x02:	type = "ISO-8859-2";		break;
			case 0x03:	type = "ISO-8859-3";		break;
			case 0x04:	type = "ISO-8859-4";		break;
			case 0x05:	type = "ISO-8859-5";		break;
			case 0x06:	type = "ISO-8859-6";		break;
			case 0x07:	type = "ISO-8859-7";		break;
			case 0x08:	type = "ISO-8859-8";		break;
			case 0x09:	type = "ISO-8859-9";		break;
			case 0x0a:	type = "ISO-8859-10";		break;
			case 0x0b:	type = "ISO-8859-11";		break;
			case 0x0d:	type = "ISO-8859-13";		break;
			case 0x0e:	type = "ISO-8859-14";		break;
			case 0x0f:	type = "ISO-8859-15";		break;
			}
		}
		src++;
		len--;
	}

	/*
	 * Destination length should be bigger. As the worse case seems to
	 * use 3 chars for one code, use it for destlen
	 */
	destlen = len * 3;
	*dest = malloc(destlen + 1);

	/* Remove special chars */
	if (!strncasecmp(type, "ISO-8859", 8) || !strcasecmp(type, "ISO-6937")) {
		/*
		 * Handles the ISO/IEC 10646 1-byte control codes
		 * (EN 300 468 v1.11.1 Table A.1)
		 * Emphasis will be represented as: *emphased*
		 */
		tmp = malloc(len + 2);
		p = (char *)tmp;
		s = src;
		for (i = 0; i < len; i++, s++) {
			if (*s >= 0x20 && (*s < 0x80 || *s > 0x9f))
				*p++ = *s;
			else if (*s == 0x86) {
				*p++ = '*';
				emphasis = 1;
			} else if (*s == 0x87 && emphasis) {
				*p++ = '*';
				emphasis = 0;
			}
		}
		if (emphasis)
			*p++ = '*';
		*p = '\0';
	} else {
		/*
		 * FIXME: need to handle the ISO/IEC 10646 2-byte control codes
		 * (EN 300 468 v1.11.1 Table A.2)
		 */
	}

	if (tmp)
		s = tmp;
	else
		s = src;

	p = *dest;
	if (!strcasecmp(type, "ISO-6937")) {
		unsigned char *p1, *p2;

		/* Convert charset to UTF-8 using Code table 00 - Latin */
		for (p1 = (unsigned char *)s; p1 < s + len; p1++)
			for (p2 = en300468_latin_00_to_utf8[*p1].data;
			     p2 < en300468_latin_00_to_utf8[*p1].data + en300468_latin_00_to_utf8[*p1].len;
			     p2++)
				*p++ = *p2;
		*p = '\0';

		/* If desired charset is not UTF-8, prepare for conversion */
		if (strcasecmp(output_charset, "UTF-8")) {
			if (tmp)
				free(tmp);
			tmp = (unsigned char *)*dest;
			len = p - *dest;

			*dest = malloc(destlen + 1);
			type = "UTF-8";
			s = tmp;
		} else
			need_conversion = 0;

	}

	/* Convert from original charset to the desired one */
	if (need_conversion) {
		char out_cs[strlen(output_charset) + 1 + sizeof(CS_OPTIONS)];

		p = *dest;
		strcpy(out_cs, output_charset);
		strcat(out_cs, CS_OPTIONS);

		iconv_t cd = iconv_open(out_cs, type);
		if (cd == (iconv_t)(-1)) {
			memcpy(p, s, len);
			p[len] = '\0';
			warning("Conversion from %s to %s not supported\n",
				 type, output_charset);
		} else {
			iconv(cd, (char **)&s, &len, &p, &destlen);
			iconv_close(cd);
			*p = '\0';
		}
	}

	if (tmp)
		free(tmp);
}

static void parse_service_descriptor (const unsigned char *buf, struct service *s)
{
	unsigned char len;

	s->type = buf[2];

	buf += 3;
	len = *buf;
	buf++;
	descriptorcpy(&s->provider_name, buf, len);

	buf += len;
	len = *buf;
	buf++;
	descriptorcpy(&s->service_name, buf, len);

	info("0x%04x 0x%04x: pmt_pid 0x%04x %s -- %s (%s%s)\n",
	    s->transport_stream_id,
	    s->service_id,
	    s->pmt_pid,
	    s->provider_name, s->service_name,
	    s->running == RM_NOT_RUNNING ? "not running" :
	    s->running == RM_STARTS_SOON ? "starts soon" :
	    s->running == RM_PAUSING     ? "pausing" :
	    s->running == RM_RUNNING     ? "running" : "???",
	    s->scrambled ? ", scrambled" : "");
}

static int find_descriptor(uint8_t tag, const unsigned char *buf,
		int descriptors_loop_len,
		const unsigned char **desc, int *desc_len)
{
	while (descriptors_loop_len > 0) {
		unsigned char descriptor_tag = buf[0];
		unsigned char descriptor_len = buf[1] + 2;

		if (!descriptor_len) {
			warning("descriptor_tag == 0x%02x, len is 0\n", descriptor_tag);
			break;
		}

		if (tag == descriptor_tag) {
			if (desc)
				*desc = buf;
			if (desc_len)
				*desc_len = descriptor_len;
			return 1;
		}

		buf += descriptor_len;
		descriptors_loop_len -= descriptor_len;
	}
	return 0;
}

static void parse_descriptors(enum table_type t, const unsigned char *buf,
			      int descriptors_loop_len, void *data)
{
	while (descriptors_loop_len > 0) {
		unsigned char descriptor_tag = buf[0];
		unsigned char descriptor_len = buf[1] + 2;

		if (!descriptor_len) {
			warning("descriptor_tag == 0x%02x, len is 0\n", descriptor_tag);
			break;
		}

		switch (descriptor_tag) {
		case 0x0a:
			if (t == PMT)
				parse_iso639_language_descriptor (buf, data);
			break;

		case 0x40:
			if (t == NIT)
				parse_network_name_descriptor (buf, data);
			break;

		case 0x43:
			if (t == NIT)
				parse_satellite_delivery_system_descriptor (buf, data);
			break;

		case 0x44:
			if (t == NIT)
				parse_cable_delivery_system_descriptor (buf, data);
			break;

		case 0x48:
			if (t == SDT)
				parse_service_descriptor (buf, data);
			break;

		case 0x53:
			if (t == SDT)
				parse_ca_identifier_descriptor (buf, data);
			break;

		case 0x5a:
			if (t == NIT)
				parse_terrestrial_delivery_system_descriptor (buf, data);
			break;

		case 0x62:
			if (t == NIT)
				parse_frequency_list_descriptor (buf, data);
			break;

		case 0x83:
			/* 0x83 is in the privately defined range of descriptor tags,
			 * so we parse this only if the user says so to avoid
			 * problems when 0x83 is something entirely different... */
			if (t == NIT && vdr_dump_channum)
				parse_terrestrial_uk_channel_number (buf, data);
			break;

		default:
			verbosedebug("skip descriptor 0x%02x\n", descriptor_tag);
		};

		buf += descriptor_len;
		descriptors_loop_len -= descriptor_len;
	}
}


static void parse_pat(const unsigned char *buf, int section_length,
		      int transport_stream_id)
{
	(void)transport_stream_id;

	while (section_length > 0) {
		struct service *s;
		int service_id = (buf[0] << 8) | buf[1];

		if (service_id == 0)
			goto skip;	/* nit pid entry */

		/* SDT might have been parsed first... */
		s = find_service(current_tp, service_id);
		if (!s)
			s = alloc_service(current_tp, service_id);
		s->pmt_pid = ((buf[2] & 0x1f) << 8) | buf[3];
		if (!s->priv && s->pmt_pid) {
			s->priv = malloc(sizeof(struct section_buf));
			setup_filter(s->priv, demux_devname,
				     s->pmt_pid, 0x02, s->service_id, 1, 0, 5);

			add_filter (s->priv);
		}

skip:
		buf += 4;
		section_length -= 4;
	};
}


static void parse_pmt (const unsigned char *buf, int section_length, int service_id)
{
	int program_info_len;
	struct service *s;
        char msg_buf[14 * AUDIO_CHAN_MAX + 1];
        char *tmp;
        int i;

	s = find_service (current_tp, service_id);
	if (!s) {
		error("PMT for serivce_id 0x%04x was not in PAT\n", service_id);
		return;
	}

	s->pcr_pid = ((buf[0] & 0x1f) << 8) | buf[1];

	program_info_len = ((buf[2] & 0x0f) << 8) | buf[3];

	buf += program_info_len + 4;
	section_length -= program_info_len + 4;

	while (section_length >= 5) {
		int ES_info_len = ((buf[3] & 0x0f) << 8) | buf[4];
		int elementary_pid = ((buf[1] & 0x1f) << 8) | buf[2];

		switch (buf[0]) {
		case 0x01:
		case 0x02:
		case 0x1b: /* H.264 video stream */
			moreverbose("  VIDEO     : PID 0x%04x\n", elementary_pid);
			if (s->video_pid == 0)
				s->video_pid = elementary_pid;
			break;
		case 0x03:
		case 0x81: /* Audio per ATSC A/53B [2] Annex B */
		case 0x0f: /* ADTS Audio Stream - usually AAC */
		case 0x11: /* ISO/IEC 14496-3 Audio with LATM transport */
		case 0x04:
			moreverbose("  AUDIO     : PID 0x%04x\n", elementary_pid);
			if (s->audio_num < AUDIO_CHAN_MAX) {
				s->audio_pid[s->audio_num] = elementary_pid;
				parse_descriptors (PMT, buf + 5, ES_info_len, s);
				s->audio_num++;
			}
			else
				warning("more than %i audio channels, truncating\n",
				     AUDIO_CHAN_MAX);
			break;
		case 0x07:
			moreverbose("  MHEG      : PID 0x%04x\n", elementary_pid);
			break;
		case 0x0B:
			moreverbose("  DSM-CC    : PID 0x%04x\n", elementary_pid);
			break;
		case 0x06:
			if (find_descriptor(0x56, buf + 5, ES_info_len, NULL, NULL)) {
				moreverbose("  TELETEXT  : PID 0x%04x\n", elementary_pid);
				s->teletext_pid = elementary_pid;
				break;
			}
			else if (find_descriptor(0x59, buf + 5, ES_info_len, NULL, NULL)) {
				/* Note: The subtitling descriptor can also signal
				 * teletext subtitling, but then the teletext descriptor
				 * will also be present; so we can be quite confident
				 * that we catch DVB subtitling streams only here, w/o
				 * parsing the descriptor. */
				moreverbose("  SUBTITLING: PID 0x%04x\n", elementary_pid);
				s->subtitling_pid = elementary_pid;
				break;
			}
			else if (find_descriptor(0x6a, buf + 5, ES_info_len, NULL, NULL)) {
				moreverbose("  AC3       : PID 0x%04x\n", elementary_pid);
				s->ac3_pid = elementary_pid;
				break;
			}
			/* fall through */
		default:
			moreverbose("  OTHER     : PID 0x%04x TYPE 0x%02x\n", elementary_pid, buf[0]);
		};

		buf += ES_info_len + 5;
		section_length -= ES_info_len + 5;
	};


	tmp = msg_buf;
	tmp += sprintf(tmp, "0x%04x (%.4s)", s->audio_pid[0], s->audio_lang[0]);

	if (s->audio_num > AUDIO_CHAN_MAX) {
		warning("more than %i audio channels: %i, truncating to %i\n",
		      AUDIO_CHAN_MAX, s->audio_num, AUDIO_CHAN_MAX);
		s->audio_num = AUDIO_CHAN_MAX;
	}

        for (i=1; i<s->audio_num; i++)
                tmp += sprintf(tmp, ", 0x%04x (%.4s)", s->audio_pid[i], s->audio_lang[i]);

        debug("0x%04x 0x%04x: %s -- %s, pmt_pid 0x%04x, vpid 0x%04x, apid %s\n",
	    s->transport_stream_id,
	    s->service_id,
	    s->provider_name, s->service_name,
	    s->pmt_pid, s->video_pid, msg_buf);
}


static void parse_nit (const unsigned char *buf, int section_length, int network_id)
{
	int descriptors_loop_len = ((buf[0] & 0x0f) << 8) | buf[1];

	if (section_length < descriptors_loop_len + 4)
	{
		warning("section too short: network_id == 0x%04x, section_length == %i, "
		     "descriptors_loop_len == %i\n",
		     network_id, section_length, descriptors_loop_len);
		return;
	}

	parse_descriptors (NIT, buf + 2, descriptors_loop_len, NULL);

	section_length -= descriptors_loop_len + 4;
	buf += descriptors_loop_len + 4;

	while (section_length > 6) {
		int transport_stream_id = (buf[0] << 8) | buf[1];
		struct transponder *t, tn;

		descriptors_loop_len = ((buf[4] & 0x0f) << 8) | buf[5];

		if (section_length < descriptors_loop_len + 4)
		{
			warning("section too short: transport_stream_id == 0x%04x, "
			     "section_length == %i, descriptors_loop_len == %i\n",
			     transport_stream_id, section_length,
			     descriptors_loop_len);
			break;
		}

		debug("transport_stream_id 0x%04x\n", transport_stream_id);

		memset(&tn, 0, sizeof(tn));
		tn.type = -1;
		tn.network_id = network_id;
		tn.original_network_id = (buf[2] << 8) | buf[3];
		tn.transport_stream_id = transport_stream_id;

		parse_descriptors (NIT, buf + 6, descriptors_loop_len, &tn);

		if (tn.type == fe_info.type) {
			/* only add if develivery_descriptor matches FE type */
			t = find_transponder(tn.param.frequency);
			if (!t)
				t = alloc_transponder(tn.param.frequency);
			copy_transponder(t, &tn);
		}

		section_length -= descriptors_loop_len + 6;
		buf += descriptors_loop_len + 6;
	}
}


static void parse_sdt (const unsigned char *buf, int section_length,
		int transport_stream_id)
{
	(void)transport_stream_id;

	buf += 3;	       /*  skip original network id + reserved field */

	while (section_length >= 5) {
		int service_id = (buf[0] << 8) | buf[1];
		int descriptors_loop_len = ((buf[3] & 0x0f) << 8) | buf[4];
		struct service *s;

		if (section_length < descriptors_loop_len)
		{
			warning("section too short: service_id == 0x%02x, section_length == %i, "
			     "descriptors_loop_len == %i\n",
			     service_id, section_length,
			     descriptors_loop_len);
			break;
		}

		s = find_service(current_tp, service_id);
		if (!s)
			/* maybe PAT has not yet been parsed... */
			s = alloc_service(current_tp, service_id);

		s->running = (buf[3] >> 5) & 0x7;
		s->scrambled = (buf[3] >> 4) & 1;

		parse_descriptors (SDT, buf + 5, descriptors_loop_len, s);

		section_length -= descriptors_loop_len + 5;
		buf += descriptors_loop_len + 5;
	};
}

/* ATSC PSIP VCT */
static void parse_atsc_service_loc_desc(struct service *s,const unsigned char *buf)
{
	struct ATSC_service_location_descriptor d = read_ATSC_service_location_descriptor(buf);
	int i;
	unsigned char *b = (unsigned char *) buf+5;

	s->pcr_pid = d.PCR_PID;
	for (i=0; i < d.number_elements; i++) {
		struct ATSC_service_location_element e = read_ATSC_service_location_element(b);
		switch (e.stream_type) {
			case 0x02: /* video */
				s->video_pid = e.elementary_PID;
				moreverbose("  VIDEO     : PID 0x%04x\n", e.elementary_PID);
				break;
			case 0x81: /* ATSC audio */
				if (s->audio_num < AUDIO_CHAN_MAX) {
					s->audio_pid[s->audio_num] = e.elementary_PID;
					s->audio_lang[s->audio_num][0] = (e.ISO_639_language_code >> 16) & 0xff;
					s->audio_lang[s->audio_num][1] = (e.ISO_639_language_code >> 8)  & 0xff;
					s->audio_lang[s->audio_num][2] =  e.ISO_639_language_code        & 0xff;
					s->audio_num++;
				}
				moreverbose("  AUDIO     : PID 0x%04x lang: %s\n",e.elementary_PID,s->audio_lang[s->audio_num-1]);

				break;
			default:
				warning("unhandled stream_type: %x\n",e.stream_type);
				break;
		};
		b += 6;
	}
}

static void parse_atsc_ext_chan_name_desc(struct service *s,const unsigned char *buf)
{
	unsigned char *b = (unsigned char *) buf+2;
	int i,j;
	int num_str = b[0];

	b++;
	for (i = 0; i < num_str; i++) {
		int num_seg = b[3];
		b += 4; /* skip lang code */
		for (j = 0; j < num_seg; j++) {
			int comp_type = b[0],/* mode = b[1],*/ num_bytes = b[2];

			switch (comp_type) {
				case 0x00:
					if (s->service_name)
						free(s->service_name);
					s->service_name = malloc(num_bytes * sizeof(char) + 1);
					memcpy(s->service_name,&b[3],num_bytes);
					s->service_name[num_bytes] = '\0';
					break;
				default:
					warning("compressed strings are not supported yet\n");
					break;
			}
			b += 3 + num_bytes;
		}
	}
}

static void parse_psip_descriptors(struct service *s,const unsigned char *buf,int len)
{
	unsigned char *b = (unsigned char *) buf;
	int desc_len;
	while (len > 0) {
		desc_len = b[1];
		switch (b[0]) {
			case ATSC_SERVICE_LOCATION_DESCRIPTOR_ID:
				parse_atsc_service_loc_desc(s,b);
				break;
			case ATSC_EXTENDED_CHANNEL_NAME_DESCRIPTOR_ID:
				parse_atsc_ext_chan_name_desc(s,b);
				break;
			default:
				warning("unhandled psip descriptor: %02x\n",b[0]);
				break;
		}
		b += 2 + desc_len;
		len -= 2 + desc_len;
	}
}

static void parse_psip_vct (const unsigned char *buf, int section_length,
		int table_id, int transport_stream_id)
{
	(void)section_length;
	(void)table_id;
	(void)transport_stream_id;

/*	int protocol_version = buf[0];*/
	int num_channels_in_section = buf[1];
	int i;
	int pseudo_id = 0xffff;
	unsigned char *b = (unsigned char *) buf + 2;

	for (i = 0; i < num_channels_in_section; i++) {
		struct service *s;
		struct tvct_channel ch = read_tvct_channel(b);

		switch (ch.service_type) {
			case 0x01:
				info("analog channels won't be put info channels.conf\n");
				break;
			case 0x02: /* ATSC TV */
			case 0x03: /* ATSC Radio */
				break;
			case 0x04: /* ATSC Data */
			default:
				continue;
		}

		if (ch.program_number == 0)
			ch.program_number = --pseudo_id;

		s = find_service(current_tp, ch.program_number);
		if (!s)
			s = alloc_service(current_tp, ch.program_number);

		if (s->service_name)
			free(s->service_name);

		s->service_name = malloc(7*sizeof(unsigned char));
		/* TODO find a better solution to convert UTF-16 */
		s->service_name[0] = ch.short_name0;
		s->service_name[1] = ch.short_name1;
		s->service_name[2] = ch.short_name2;
		s->service_name[3] = ch.short_name3;
		s->service_name[4] = ch.short_name4;
		s->service_name[5] = ch.short_name5;
		s->service_name[6] = ch.short_name6;

		parse_psip_descriptors(s,&b[32],ch.descriptors_length);

		s->channel_num = ch.major_channel_number << 10 | ch.minor_channel_number;

		if (ch.hidden) {
			s->running = RM_NOT_RUNNING;
			info("service is not running, pseudo program_number.");
		} else {
			s->running = RM_RUNNING;
			info("service is running.");
		}

		info(" Channel number: %d:%d. Name: '%s'\n",
			ch.major_channel_number, ch.minor_channel_number,s->service_name);

		b += 32 + ch.descriptors_length;
	}
}

static int get_bit (uint8_t *bitfield, int bit)
{
	return (bitfield[bit/8] >> (bit % 8)) & 1;
}

static void set_bit (uint8_t *bitfield, int bit)
{
	bitfield[bit/8] |= 1 << (bit % 8);
}


/**
 *   returns 0 when more sections are expected
 *	   1 when all sections are read on this pid
 *	   -1 on invalid table id
 */
static int parse_section (struct section_buf *s)
{
	const unsigned char *buf = s->buf;
	int table_id;
	int section_length;
	int table_id_ext;
	int section_version_number;
	int section_number;
	int last_section_number;
	int i;

	table_id = buf[0];

	if (s->table_id != table_id)
		return -1;

	section_length = ((buf[1] & 0x0f) << 8) | buf[2];

	table_id_ext = (buf[3] << 8) | buf[4];
	section_version_number = (buf[5] >> 1) & 0x1f;
	section_number = buf[6];
	last_section_number = buf[7];

	if (s->segmented && s->table_id_ext != -1 && s->table_id_ext != table_id_ext) {
		/* find or allocate actual section_buf matching table_id_ext */
		while (s->next_seg) {
			s = s->next_seg;
			if (s->table_id_ext == table_id_ext)
				break;
		}
		if (s->table_id_ext != table_id_ext) {
			assert(s->next_seg == NULL);
			s->next_seg = calloc(1, sizeof(struct section_buf));
			s->next_seg->segmented = s->segmented;
			s->next_seg->run_once = s->run_once;
			s->next_seg->timeout = s->timeout;
			s = s->next_seg;
			s->table_id = table_id;
			s->table_id_ext = table_id_ext;
			s->section_version_number = section_version_number;
		}
	}

	if (s->section_version_number != section_version_number ||
			s->table_id_ext != table_id_ext) {
		struct section_buf *next_seg = s->next_seg;

		if (s->section_version_number != -1 && s->table_id_ext != -1)
			debug("section version_number or table_id_ext changed "
				"%d -> %d / %04x -> %04x\n",
				s->section_version_number, section_version_number,
				s->table_id_ext, table_id_ext);
		s->table_id_ext = table_id_ext;
		s->section_version_number = section_version_number;
		s->sectionfilter_done = 0;
		memset (s->section_done, 0, sizeof(s->section_done));
		s->next_seg = next_seg;
	}

	buf += 8;			/* past generic table header */
	section_length -= 5 + 4;	/* header + crc */
	if (section_length < 0) {
		warning("truncated section (PID 0x%04x, lenght %d)",
			s->pid, section_length + 9);
		return 0;
	}

	if (!get_bit(s->section_done, section_number)) {
		set_bit (s->section_done, section_number);

		debug("pid 0x%02x tid 0x%02x table_id_ext 0x%04x, "
		    "%i/%i (version %i)\n",
		    s->pid, table_id, table_id_ext, section_number,
		    last_section_number, section_version_number);

		switch (table_id) {
		case 0x00:
			verbose("PAT\n");
			parse_pat (buf, section_length, table_id_ext);
			break;

		case 0x02:
			verbose("PMT 0x%04x for service 0x%04x\n", s->pid, table_id_ext);
			parse_pmt (buf, section_length, table_id_ext);
			break;

		case 0x41:
			verbose("////////////////////////////////////////////// NIT other\n");
		case 0x40:
			verbose("NIT (%s TS)\n", table_id == 0x40 ? "actual":"other");
			parse_nit (buf, section_length, table_id_ext);
			break;

		case 0x42:
		case 0x46:
			verbose("SDT (%s TS)\n", table_id == 0x42 ? "actual":"other");
			parse_sdt (buf, section_length, table_id_ext);
			break;

		case 0xc8:
		case 0xc9:
			verbose("ATSC VCT\n");
			parse_psip_vct(buf, section_length, table_id, table_id_ext);
			break;
		default:
			;
		};

		for (i = 0; i <= last_section_number; i++)
			if (get_bit (s->section_done, i) == 0)
				break;

		if (i > last_section_number)
			s->sectionfilter_done = 1;
	}

	if (s->segmented) {
		/* always wait for timeout; this is because we don't now how
		 * many segments there are
		 */
		return 0;
	}
	else if (s->sectionfilter_done)
		return 1;

	return 0;
}


static int read_sections (struct section_buf *s)
{
	int section_length, count;

	if (s->sectionfilter_done && !s->segmented)
		return 1;

	/* the section filter API guarantess that we get one full section
	 * per read(), provided that the buffer is large enough (it is)
	 */
	if (((count = read (s->fd, s->buf, sizeof(s->buf))) < 0) && errno == EOVERFLOW)
		count = read (s->fd, s->buf, sizeof(s->buf));
	if (count < 0) {
		errorn("read_sections: read error");
		return -1;
	}

	if (count < 4)
		return -1;

	section_length = ((s->buf[1] & 0x0f) << 8) | s->buf[2];

	if (count != section_length + 3)
		return -1;

	if (parse_section(s) == 1)
		return 1;

	return 0;
}


static LIST_HEAD(running_filters);
static LIST_HEAD(waiting_filters);
static int n_running;
#define MAX_RUNNING 27
static struct pollfd poll_fds[MAX_RUNNING];
static struct section_buf* poll_section_bufs[MAX_RUNNING];


static void setup_filter (struct section_buf* s, const char *dmx_devname,
			  int pid, int tid, int tid_ext,
			  int run_once, int segmented, int timeout)
{
	memset (s, 0, sizeof(struct section_buf));

	s->fd = -1;
	s->dmx_devname = dmx_devname;
	s->pid = pid;
	s->table_id = tid;

	s->run_once = run_once;
	s->segmented = segmented;

	if (long_timeout)
		s->timeout = 5 * timeout;
	else
		s->timeout = timeout;

	s->table_id_ext = tid_ext;
	s->section_version_number = -1;

	INIT_LIST_HEAD (&s->list);
}

static void update_poll_fds(void)
{
	struct list_head *p;
	struct section_buf* s;
	int i;

	memset(poll_section_bufs, 0, sizeof(poll_section_bufs));
	for (i = 0; i < MAX_RUNNING; i++)
		poll_fds[i].fd = -1;
	i = 0;
	list_for_each (p, &running_filters) {
		if (i >= MAX_RUNNING)
			fatal("too many poll_fds\n");
		s = list_entry (p, struct section_buf, list);
		if (s->fd == -1)
			fatal("s->fd == -1 on running_filters\n");
		verbosedebug("poll fd %d\n", s->fd);
		poll_fds[i].fd = s->fd;
		poll_fds[i].events = POLLIN;
		poll_fds[i].revents = 0;
		poll_section_bufs[i] = s;
		i++;
	}
	if (i != n_running)
		fatal("n_running is hosed\n");
}

static int start_filter (struct section_buf* s)
{
	struct dmx_sct_filter_params f;

	if (n_running >= MAX_RUNNING)
		goto err0;
	if ((s->fd = open (s->dmx_devname, O_RDWR | O_NONBLOCK)) < 0)
		goto err0;

	verbosedebug("start filter pid 0x%04x table_id 0x%02x\n", s->pid, s->table_id);

	memset(&f, 0, sizeof(f));

	f.pid = (uint16_t) s->pid;

	if (s->table_id < 0x100 && s->table_id > 0) {
		f.filter.filter[0] = (uint8_t) s->table_id;
		f.filter.mask[0]   = 0xff;
	}
	if (s->table_id_ext < 0x10000 && s->table_id_ext > 0) {
		f.filter.filter[1] = (uint8_t) ((s->table_id_ext >> 8) & 0xff);
		f.filter.filter[2] = (uint8_t) (s->table_id_ext & 0xff);
		f.filter.mask[1] = 0xff;
		f.filter.mask[2] = 0xff;
	}

	f.timeout = 0;
	f.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC;

	if (ioctl(s->fd, DMX_SET_FILTER, &f) == -1) {
		errorn ("ioctl DMX_SET_FILTER failed");
		goto err1;
	}

	s->sectionfilter_done = 0;
	time(&s->start_time);

	list_del_init (&s->list);  /* might be in waiting filter list */
	list_add (&s->list, &running_filters);

	n_running++;
	update_poll_fds();

	return 0;

err1:
	ioctl (s->fd, DMX_STOP);
	close (s->fd);
err0:
	return -1;
}


static void stop_filter (struct section_buf *s)
{
	verbosedebug("stop filter pid 0x%04x\n", s->pid);
	ioctl (s->fd, DMX_STOP);
	close (s->fd);
	s->fd = -1;
	list_del (&s->list);
	s->running_time += time(NULL) - s->start_time;

	n_running--;
	update_poll_fds();
}


static void add_filter (struct section_buf *s)
{
	verbosedebug("add filter pid 0x%04x\n", s->pid);
	if (start_filter (s))
		list_add_tail (&s->list, &waiting_filters);
}


static void remove_filter (struct section_buf *s)
{
	verbosedebug("remove filter pid 0x%04x\n", s->pid);
	stop_filter (s);

	while (!list_empty(&waiting_filters)) {
		struct list_head *next = waiting_filters.next;
		s = list_entry (next, struct section_buf, list);
		if (start_filter (s))
			break;
	};
}


static void read_filters (void)
{
	struct section_buf *s;
	int i, n, done;

	n = poll(poll_fds, n_running, 1000);
	if (n == -1)
		errorn("poll");

	for (i = 0; i < n_running; i++) {
		s = poll_section_bufs[i];
		if (!s)
			fatal("poll_section_bufs[%d] is NULL\n", i);
		if (poll_fds[i].revents)
			done = read_sections (s) == 1;
		else
			done = 0; /* timeout */
		if (done || time(NULL) > s->start_time + s->timeout) {
			if (s->run_once) {
				if (done)
					verbosedebug("filter done pid 0x%04x\n", s->pid);
				else
					warning("filter timeout pid 0x%04x\n", s->pid);
				remove_filter (s);
			}
		}
	}
}


static int mem_is_zero (const void *mem, int size)
{
	const char *p = mem;
	int i;

	for (i=0; i<size; i++) {
		if (p[i] != 0x00)
			return 0;
	}

	return 1;
}


static int switch_pos = 0;

static int __tune_to_transponder (int frontend_fd, struct transponder *t)
{
	struct dvb_frontend_parameters p;
	fe_status_t s;
	int i;

	current_tp = t;

	if (mem_is_zero (&t->param, sizeof(struct dvb_frontend_parameters)))
		return -1;

	memcpy (&p, &t->param, sizeof(struct dvb_frontend_parameters));

	if (verbosity >= 1) {
		dprintf(1, ">>> tune to: ");
		dump_dvb_parameters (stderr, t);
		if (t->last_tuning_failed)
			dprintf(1, " (tuning failed)");
		dprintf(1, "\n");
	}

	if (t->type == FE_QPSK) {
		if (lnb_type.high_val) {
			if (lnb_type.switch_val) {
				/* Voltage-controlled switch */
				int hiband = 0;

				if (p.frequency >= lnb_type.switch_val)
					hiband = 1;

				setup_switch (frontend_fd,
					      switch_pos,
					      t->polarisation == POLARISATION_VERTICAL ? 0 : 1,
					      hiband);
				usleep(50000);
				if (hiband)
					p.frequency = abs(p.frequency - lnb_type.high_val);
				else
					p.frequency = abs(p.frequency - lnb_type.low_val);
			} else {
				/* C-Band Multipoint LNBf */
				p.frequency = abs(p.frequency - (t->polarisation == POLARISATION_VERTICAL ?
						lnb_type.low_val: lnb_type.high_val));
			}
		} else	{
			/* Monopoint LNBf without switch */
			p.frequency = abs(p.frequency - lnb_type.low_val);
		}
		if (verbosity >= 2)
			dprintf(1,"DVB-S IF freq is %d\n",p.frequency);
	}

	if (ioctl(frontend_fd, FE_SET_FRONTEND, &p) == -1) {
		errorn("Setting frontend parameters failed");
		return -1;
	}

	for (i = 0; i < 10; i++) {
		usleep (200000);

		if (ioctl(frontend_fd, FE_READ_STATUS, &s) == -1) {
			errorn("FE_READ_STATUS failed");
			return -1;
		}

		verbose(">>> tuning status == 0x%02x\n", s);

		if (s & FE_HAS_LOCK) {
			t->last_tuning_failed = 0;
			return 0;
		}
	}

	warning(">>> tuning failed!!!\n");

	t->last_tuning_failed = 1;

	return -1;
}

static int set_delivery_system(int fd, unsigned type)
{
	struct dtv_properties props;
	struct dtv_property dvb_prop[1];
	unsigned delsys;

	switch (type) {
		case FE_QPSK:
			delsys = SYS_DVBS;
			break;
		case FE_QAM:
			delsys = SYS_DVBC_ANNEX_AC;
			break;
		case FE_OFDM:
			delsys = SYS_DVBT;
			break;
		case FE_ATSC:
			delsys = SYS_ATSC;
			break;
		default:
			return -1;
	}

	dvb_prop[0].cmd = DTV_DELIVERY_SYSTEM;
	dvb_prop[0].u.data = delsys;
	props.num = 1;
	props.props = dvb_prop;
	if (ioctl(fd, FE_SET_PROPERTY, &props) >= 0)
		return 0;
	return errno;
}

static int tune_to_transponder (int frontend_fd, struct transponder *t)
{
	int rc;

	/* move TP from "new" to "scanned" list */
	list_del_init(&t->list);
	list_add_tail(&t->list, &scanned_transponders);
	t->scan_done = 1;

	if (t->type != fe_info.type) {
		rc = set_delivery_system(frontend_fd, t->type);
		if (!rc)
			fe_info.type = t->type;
	}

	if (t->type != fe_info.type) {
		warning("frontend type (%s) is not compatible with requested tuning type (%s)\n",
				fe_type2str(fe_info.type),fe_type2str(t->type));
		/* ignore cable descriptors in sat NIT and vice versa */
		t->last_tuning_failed = 1;
		return -1;
	}

	if (__tune_to_transponder (frontend_fd, t) == 0)
		return 0;

	return __tune_to_transponder (frontend_fd, t);
}


static int tune_to_next_transponder (int frontend_fd)
{
	struct list_head *pos, *tmp;
	struct transponder *t, *to;
	uint32_t freq;

	list_for_each_safe(pos, tmp, &new_transponders) {
		t = list_entry (pos, struct transponder, list);
retry:
		if (tune_to_transponder (frontend_fd, t) == 0)
			return 0;
next:
		if (t->other_frequency_flag && t->other_f && t->n_other_f) {
			/* check if the alternate freqeuncy is really new to us */
			freq = t->other_f[t->n_other_f - 1];
			t->n_other_f--;
			if (find_transponder(freq))
				goto next;

			/* remember tuning to the old frequency failed */
			to = calloc(1, sizeof(*to));
			to->param.frequency = t->param.frequency;
			to->wrong_frequency = 1;
			INIT_LIST_HEAD(&to->list);
			INIT_LIST_HEAD(&to->services);
			list_add_tail(&to->list, &scanned_transponders);
			copy_transponder(to, t);

			t->param.frequency = freq;
			info("retrying with f=%d\n", t->param.frequency);
			goto retry;
		}
	}
	return -1;
}

struct strtab {
	const char *str;
	int val;
};
static int str2enum(const char *str, const struct strtab *tab, int deflt)
{
	while (tab->str) {
		if (!strcmp(tab->str, str))
			return tab->val;
		tab++;
	}
	error("invalid enum value '%s'\n", str);
	return deflt;
}

static const char * enum2str(int v, const struct strtab *tab, const char *deflt)
{
	while (tab->str) {
		if (v == tab->val)
			return tab->str;
		tab++;
	}
	error("invalid enum value '%d'\n", v);
	return deflt;
}

static enum fe_code_rate str2fec(const char *fec)
{
	struct strtab fectab[] = {
		{ "NONE", FEC_NONE },
		{ "1/2",  FEC_1_2 },
		{ "2/3",  FEC_2_3 },
		{ "3/4",  FEC_3_4 },
		{ "4/5",  FEC_4_5 },
		{ "5/6",  FEC_5_6 },
		{ "6/7",  FEC_6_7 },
		{ "7/8",  FEC_7_8 },
		{ "8/9",  FEC_8_9 },
		{ "AUTO", FEC_AUTO },
		{ NULL, 0 }
	};
	return str2enum(fec, fectab, FEC_AUTO);
}

static enum fe_modulation str2qam(const char *qam)
{
	struct strtab qamtab[] = {
		{ "QPSK",   QPSK },
		{ "QAM16",  QAM_16 },
		{ "QAM32",  QAM_32 },
		{ "QAM64",  QAM_64 },
		{ "QAM128", QAM_128 },
		{ "QAM256", QAM_256 },
		{ "AUTO",   QAM_AUTO },
		{ "8VSB",   VSB_8 },
		{ "16VSB",  VSB_16 },
		{ NULL, 0 }
	};
	return str2enum(qam, qamtab, QAM_AUTO);
}

static enum fe_bandwidth str2bandwidth(const char *bw)
{
	struct strtab bwtab[] = {
		{ "8MHz", BANDWIDTH_8_MHZ },
		{ "7MHz", BANDWIDTH_7_MHZ },
		{ "6MHz", BANDWIDTH_6_MHZ },
		{ "AUTO", BANDWIDTH_AUTO },
		{ NULL, 0 }
	};
	return str2enum(bw, bwtab, BANDWIDTH_AUTO);
}

static enum fe_transmit_mode str2mode(const char *mode)
{
	struct strtab modetab[] = {
		{ "2k",   TRANSMISSION_MODE_2K },
		{ "8k",   TRANSMISSION_MODE_8K },
		{ "AUTO", TRANSMISSION_MODE_AUTO },
		{ NULL, 0 }
	};
	return str2enum(mode, modetab, TRANSMISSION_MODE_AUTO);
}

static enum fe_guard_interval str2guard(const char *guard)
{
	struct strtab guardtab[] = {
		{ "1/32", GUARD_INTERVAL_1_32 },
		{ "1/16", GUARD_INTERVAL_1_16 },
		{ "1/8",  GUARD_INTERVAL_1_8 },
		{ "1/4",  GUARD_INTERVAL_1_4 },
		{ "AUTO", GUARD_INTERVAL_AUTO },
		{ NULL, 0 }
	};
	return str2enum(guard, guardtab, GUARD_INTERVAL_AUTO);
}

static enum fe_hierarchy str2hier(const char *hier)
{
	struct strtab hiertab[] = {
		{ "NONE", HIERARCHY_NONE },
		{ "1",    HIERARCHY_1 },
		{ "2",    HIERARCHY_2 },
		{ "4",    HIERARCHY_4 },
		{ "AUTO", HIERARCHY_AUTO },
		{ NULL, 0 }
	};
	return str2enum(hier, hiertab, HIERARCHY_AUTO);
}

static const char * fe_type2str(fe_type_t t)
{
	struct strtab typetab[] = {
		{ "QPSK", FE_QPSK,},
		{ "QAM",  FE_QAM, },
		{ "OFDM", FE_OFDM,},
		{ "ATSC", FE_ATSC,},
		{ NULL, 0 }
	};

	return enum2str(t, typetab, "UNK");
}

static int tune_initial (int frontend_fd, const char *initial)
{
	FILE *inif;
	unsigned int f, sr;
	char buf[200];
	char pol[20], fec[20], qam[20], bw[20], fec2[20], mode[20], guard[20], hier[20];
	struct transponder *t;

	inif = fopen(initial, "r");
	if (!inif) {
		error("cannot open '%s': %d %m\n", initial, errno);
		return -1;
	}
	while (fgets(buf, sizeof(buf), inif)) {
		if (buf[0] == '#' || buf[0] == '\n')
			;
		else if (sscanf(buf, "S %u %1[HVLR] %u %4s\n", &f, pol, &sr, fec) == 4) {
			t = alloc_transponder(f);
			t->type = FE_QPSK;
			switch(pol[0]) {
				case 'H':
				case 'L':
					t->polarisation = POLARISATION_HORIZONTAL;
					break;
				default:
					t->polarisation = POLARISATION_VERTICAL;;
					break;
			}
			t->param.inversion = spectral_inversion;
			t->param.u.qpsk.symbol_rate = sr;
			t->param.u.qpsk.fec_inner = str2fec(fec);
			info("initial transponder %u %c %u %d\n",
					t->param.frequency,
					pol[0], sr,
					t->param.u.qpsk.fec_inner);
		}
		else if (sscanf(buf, "C %u %u %4s %6s\n", &f, &sr, fec, qam) == 4) {
			t = alloc_transponder(f);
			t->type = FE_QAM;
			t->param.inversion = spectral_inversion;
			t->param.u.qam.symbol_rate = sr;
			t->param.u.qam.fec_inner = str2fec(fec);
			t->param.u.qam.modulation = str2qam(qam);
			info("initial transponder %u %u %d %d\n",
					t->param.frequency,
					sr,
					t->param.u.qam.fec_inner,
					t->param.u.qam.modulation);
		}
		else if (sscanf(buf, "T %u %4s %4s %4s %7s %4s %4s %4s\n",
					&f, bw, fec, fec2, qam, mode, guard, hier) == 8) {
			t = alloc_transponder(f);
			t->type = FE_OFDM;
			t->param.inversion = spectral_inversion;
			t->param.u.ofdm.bandwidth = str2bandwidth(bw);
			t->param.u.ofdm.code_rate_HP = str2fec(fec);
			if (t->param.u.ofdm.code_rate_HP == FEC_NONE)
				t->param.u.ofdm.code_rate_HP = FEC_AUTO;
			t->param.u.ofdm.code_rate_LP = str2fec(fec2);
			if (t->param.u.ofdm.code_rate_LP == FEC_NONE)
				t->param.u.ofdm.code_rate_LP = FEC_AUTO;
			t->param.u.ofdm.constellation = str2qam(qam);
			t->param.u.ofdm.transmission_mode = str2mode(mode);
			t->param.u.ofdm.guard_interval = str2guard(guard);
			t->param.u.ofdm.hierarchy_information = str2hier(hier);
			info("initial transponder %u %d %d %d %d %d %d %d\n",
					t->param.frequency,
					t->param.u.ofdm.bandwidth,
					t->param.u.ofdm.code_rate_HP,
					t->param.u.ofdm.code_rate_LP,
					t->param.u.ofdm.constellation,
					t->param.u.ofdm.transmission_mode,
					t->param.u.ofdm.guard_interval,
					t->param.u.ofdm.hierarchy_information);
		}
		else if (sscanf(buf, "A %u %7s\n",
					&f,qam) == 2) {
			t = alloc_transponder(f);
			t->type = FE_ATSC;
			t->param.u.vsb.modulation = str2qam(qam);
		} else
			error("cannot parse'%s'\n", buf);
	}

	fclose(inif);

	return tune_to_next_transponder(frontend_fd);
}


static void scan_tp_atsc(void)
{
	struct section_buf s0,s1,s2;

	if (no_ATSC_PSIP) {
		setup_filter(&s0, demux_devname, 0x00, 0x00, -1, 1, 0, 5); /* PAT */
		add_filter(&s0);
	} else {
		if (ATSC_type & 0x1) {
			setup_filter(&s0, demux_devname, 0x1ffb, 0xc8, -1, 1, 0, 5); /* terrestrial VCT */
			add_filter(&s0);
		}
		if (ATSC_type & 0x2) {
			setup_filter(&s1, demux_devname, 0x1ffb, 0xc9, -1, 1, 0, 5); /* cable VCT */
			add_filter(&s1);
		}
		setup_filter(&s2, demux_devname, 0x00, 0x00, -1, 1, 0, 5); /* PAT */
		add_filter(&s2);
	}

	do {
		read_filters ();
	} while (!(list_empty(&running_filters) &&
		   list_empty(&waiting_filters)));
}

static void scan_tp_dvb (void)
{
	struct section_buf s0;
	struct section_buf s1;
	struct section_buf s2;
	struct section_buf s3;

	/**
	 *  filter timeouts > min repetition rates specified in ETR211
	 */
	setup_filter (&s0, demux_devname, 0x00, 0x00, -1, 1, 0, 5); /* PAT */
	setup_filter (&s1, demux_devname, 0x11, 0x42, -1, 1, 0, 5); /* SDT */

	add_filter (&s0);
	add_filter (&s1);

	if (!current_tp_only || output_format != OUTPUT_PIDS) {
		setup_filter (&s2, demux_devname, 0x10, 0x40, -1, 1, 0, 15); /* NIT */
		add_filter (&s2);
		if (get_other_nits) {
			/* get NIT-others
			 * Note: There is more than one NIT-other: one per
			 * network, separated by the network_id.
			 */
			setup_filter (&s3, demux_devname, 0x10, 0x41, -1, 1, 1, 15);
			add_filter (&s3);
		}
	}

	do {
		read_filters ();
	} while (!(list_empty(&running_filters) &&
		   list_empty(&waiting_filters)));
}

static void scan_tp(void)
{
	switch(fe_info.type) {
		case FE_QPSK:
		case FE_QAM:
		case FE_OFDM:
			scan_tp_dvb();
			break;
		case FE_ATSC:
			scan_tp_atsc();
			break;
		default:
			break;
	}
}

static void scan_network (int frontend_fd, const char *initial)
{
	if (tune_initial (frontend_fd, initial) < 0) {
		error("initial tuning failed\n");
		return;
	}

	do {
		scan_tp();
	} while (tune_to_next_transponder(frontend_fd) == 0);
}


static void pids_dump_service_parameter_set(FILE *f, struct service *s)
{
        int i;

	fprintf(f, "%-24.24s (0x%04x) %02x: ", s->service_name, s->service_id, s->type);
	if (!s->pcr_pid || (s->type > 2))
		fprintf(f, "           ");
	else if (s->pcr_pid == s->video_pid)
		fprintf(f, "PCR == V   ");
	else if ((s->audio_num == 1) && (s->pcr_pid == s->audio_pid[0]))
		fprintf(f, "PCR == A   ");
	else
		fprintf(f, "PCR 0x%04x ", s->pcr_pid);
	if (s->video_pid)
		fprintf(f, "V 0x%04x", s->video_pid);
	else
		fprintf(f, "        ");
	if (s->audio_num)
		fprintf(f, " A");
        for (i = 0; i < s->audio_num; i++) {
		fprintf(f, " 0x%04x", s->audio_pid[i]);
		if (s->audio_lang[i][0])
			fprintf(f, " (%.3s)", s->audio_lang[i]);
		else if (s->audio_num == 1)
			fprintf(f, "      ");
	}
	if (s->teletext_pid)
		fprintf(f, " TT 0x%04x", s->teletext_pid);
	if (s->ac3_pid)
		fprintf(f, " AC3 0x%04x", s->ac3_pid);
	if (s->subtitling_pid)
		fprintf(f, " SUB 0x%04x", s->subtitling_pid);
	fprintf(f, "\n");
}

static char sat_polarisation (struct transponder *t)
{
	return t->polarisation == POLARISATION_VERTICAL ? 'v' : 'h';
}

static int sat_number (struct transponder *t)
{
	(void) t;

	return switch_pos;
}

static void dump_lists (void)
{
	struct list_head *p1, *p2;
	struct transponder *t;
	struct service *s;
	int n = 0, i;
	char sn[20];
        int anon_services = 0;

	list_for_each(p1, &scanned_transponders) {
		t = list_entry(p1, struct transponder, list);
		if (t->wrong_frequency)
			continue;
		list_for_each(p2, &t->services) {
			n++;
		}
	}
	info("dumping lists (%d services)\n", n);

	list_for_each(p1, &scanned_transponders) {
		t = list_entry(p1, struct transponder, list);
		if (t->wrong_frequency)
			continue;
		list_for_each(p2, &t->services) {
			s = list_entry(p2, struct service, list);

			if (!s->service_name) {
				/* not in SDT */
				if (unique_anon_services)
					snprintf(sn, sizeof(sn), "[%03x-%04x]",
						 anon_services, s->service_id);
				else
					snprintf(sn, sizeof(sn), "[%04x]",
						 s->service_id);
				s->service_name = strdup(sn);
				anon_services++;
			}
			/* ':' is field separator in szap and vdr service lists */
			for (i = 0; s->service_name[i]; i++) {
				if (s->service_name[i] == ':')
					s->service_name[i] = ' ';
			}
			for (i = 0; s->provider_name && s->provider_name[i]; i++) {
				if (s->provider_name[i] == ':')
					s->provider_name[i] = ' ';
			}
			if (s->video_pid && !(serv_select & 1))
				continue; /* no TV services */
			if (!s->video_pid && s->audio_num && !(serv_select & 2))
				continue; /* no radio services */
			if (!s->video_pid && !s->audio_num && !(serv_select & 4))
				continue; /* no data/other services */
			if (s->scrambled && !ca_select)
				continue; /* FTA only */
			switch (output_format)
			{
			  case OUTPUT_PIDS:
				pids_dump_service_parameter_set (stdout, s);
				break;
			  case OUTPUT_VDR:
				vdr_dump_service_parameter_set (stdout,
						    s->service_name,
						    s->provider_name,
						    t->type,
						    &t->param,
						    sat_polarisation(t),
						    s->video_pid,
						    s->pcr_pid,
						    s->audio_pid,
						    s->audio_lang,
						    s->audio_num,
						    s->teletext_pid,
						    s->scrambled,
						    //FIXME: s->subtitling_pid
						    s->ac3_pid,
						    s->service_id,
						    t->original_network_id,
						    s->transport_stream_id,
						    t->orbital_pos,
						    t->we_flag,
						    vdr_dump_provider,
						    ca_select,
						    vdr_version,
						    vdr_dump_channum,
						    s->channel_num);
				break;
			  case OUTPUT_ZAP:
				zap_dump_service_parameter_set (stdout,
						    s->service_name,
						    t->type,
						    &t->param,
						    sat_polarisation(t),
						    sat_number(t),
						    s->video_pid,
						    s->audio_pid,
						    s->service_id);
			  default:
				break;
			  }
		}
	}
	info("Done.\n");
}

static void show_existing_tuning_data_files(void)
{
#ifndef DATADIR
#define DATADIR "/usr/local/share"
#endif
	static const char* prefixlist[] = { DATADIR "/dvb", "/etc/dvb",
					    DATADIR "/doc/packages/dvb", 0 };
	unsigned int i;
	const char **prefix;
	fprintf(stderr, "initial tuning data files:\n");
	for (prefix = prefixlist; *prefix; prefix++) {
		glob_t globbuf;
		char* globspec = malloc (strlen(*prefix)+9);
		strcpy (globspec, *prefix); strcat (globspec, "/dvb-?/*");
		if (! glob (globspec, 0, 0, &globbuf)) {
			for (i=0; i < globbuf.gl_pathc; i++)
				fprintf(stderr, " file: %s\n", globbuf.gl_pathv[i]);
		}
		free (globspec);
		globfree (&globbuf);
	}
}

static void handle_sigint(int sig)
{
	(void)sig;
	error("interrupted by SIGINT, dumping partial result...\n");
	dump_lists();
	exit(2);
}

static const char *usage = "\n"
	"usage: %s [options...] [-c | initial-tuning-data-file]\n"
	"	atsc/dvbscan doesn't do frequency scans, hence it needs initial\n"
	"	tuning data for at least one transponder/channel.\n"
	"	-c	scan on currently tuned transponder only\n"
	"	-v 	verbose (repeat for more)\n"
	"	-q 	quiet (repeat for less)\n"
	"	-a N	use DVB /dev/dvb/adapterN/\n"
	"	-f N	use DVB /dev/dvb/adapter?/frontendN\n"
	"	-d N	use DVB /dev/dvb/adapter?/demuxN\n"
	"	-s N	use DiSEqC switch position N (DVB-S only)\n"
	"	-i N	spectral inversion setting (0: off, 1: on, 2: auto [default])\n"
	"	-n	evaluate NIT-other for full network scan (slow!)\n"
	"	-5	multiply all filter timeouts by factor 5\n"
	"		for non-DVB-compliant section repitition rates\n"
	"	-o fmt	output format: 'zap' (default), 'vdr' or 'pids' (default with -c)\n"
	"	-x N	Conditional Access, (default -1)\n"
	"		N=0 gets only FTA channels\n"
	"		N=-1 gets all channels\n"
	"		N=xxx sets ca field in vdr output to :xxx:\n"
	"	-t N	Service select, Combined bitfield parameter.\n"
	"		1 = TV, 2 = Radio, 4 = Other, (default 7)\n"
	"	-p	for vdr output format: dump provider name\n"
	"	-e N	VDR version, default 3 for VDR-1.3.x and newer\n"
	"		value 2 sets NIT and TID to zero\n"
	"		Vdr version 1.3.x and up implies -p.\n"
	"	-l lnb-type (DVB-S Only) (use -l help to print types) or \n"
	"	-l low[,high[,switch]] in Mhz\n"
	"	-u      UK DVB-T Freeview channel numbering for VDR\n\n"
	"	-P do not use ATSC PSIP tables for scanning\n"
	"	    (but only PAT and PMT) (applies for ATSC only)\n"
	"	-A N	check for ATSC 1=Terrestrial [default], 2=Cable or 3=both\n"
	"	-U	Uniquely name unknown services\n"
	"	-C cs	Override default charset for service name/provider (default = ISO-6937)\n"
	"	-D cs	Output charset (default = %s)\n"
	"Supported charsets by -C/-D parameters can be obtained via 'iconv -l' command\n";

void
bad_usage(char *pname, int problem)
{
	int i;
	struct lnb_types_st *lnbp;
	char **cp;

	switch (problem) {
	default:
	case 0:
		fprintf (stderr, usage, pname, output_charset);
		break;
	case 1:
		i = 0;
		fprintf(stderr, "-l <lnb-type> or -l low[,high[,switch]] in Mhz\n"
			"where <lnb-type> is:\n");
		while(NULL != (lnbp = lnb_enum(i))) {
			fprintf (stderr, "%s\n", lnbp->name);
			for (cp = lnbp->desc; *cp ; cp++) {
				fprintf (stderr, "   %s\n", *cp);
			}
			i++;
		}
		break;
	case 2:
		show_existing_tuning_data_files();
		fprintf (stderr, usage, pname);
	}
}

int main (int argc, char **argv)
{
	char frontend_devname [80];
	int adapter = 0, frontend = 0, demux = 0;
	int opt, i;
	int frontend_fd;
	int fe_open_mode;
	const char *initial = NULL;
	char *charset;

	if (argc <= 1) {
	    bad_usage(argv[0], 2);
	    return -1;
	}

	/*
	 * Get the environment charset, and use it as the default
	 * output charset. In thesis, using nl_langinfo should be
	 * enough, but, in my tests, it is not as reliable as checking
	 * the environment vars directly.
	 */
	if ((charset = getenv("LC_ALL")) ||
	    (charset = getenv("LC_CTYPE")) ||
	    (charset = getenv ("LANG"))) {
		while (*charset != '.' && *charset)
			charset++;
		if (*charset == '.')
			charset++;
		if (*charset)
			output_charset = charset;
		else
			output_charset = nl_langinfo(CODESET);
	} else
		output_charset = nl_langinfo(CODESET);

	/* start with default lnb type */
	lnb_type = *lnb_enum(0);
	while ((opt = getopt(argc, argv, "5cnpa:f:d:s:o:x:e:t:i:l:vquPA:UC:D:")) != -1) {
		switch (opt) {
		case 'a':
			adapter = strtoul(optarg, NULL, 0);
			break;
		case 'c':
			current_tp_only = 1;
			if (!output_format_set)
				output_format = OUTPUT_PIDS;
			break;
		case 'n':
			get_other_nits = 1;
			break;
		case 'd':
			demux = strtoul(optarg, NULL, 0);
			break;
		case 'f':
			frontend = strtoul(optarg, NULL, 0);
			break;
		case 'p':
			vdr_dump_provider = 1;
			break;
		case 's':
			switch_pos = strtoul(optarg, NULL, 0);
			break;
		case 'o':
                        if      (strcmp(optarg, "zap") == 0) output_format = OUTPUT_ZAP;
                        else if (strcmp(optarg, "vdr") == 0) output_format = OUTPUT_VDR;
                        else if (strcmp(optarg, "pids") == 0) output_format = OUTPUT_PIDS;
                        else {
				bad_usage(argv[0], 0);
				return -1;
			}
			output_format_set = 1;
			break;
		case '5':
			long_timeout = 1;
			break;
		case 'x':
			ca_select = strtoul(optarg, NULL, 0);
			break;
		case 'e':
			vdr_version = strtoul(optarg, NULL, 0);
			break;
		case 't':
			serv_select = strtoul(optarg, NULL, 0);
			break;
		case 'i':
			spectral_inversion = strtoul(optarg, NULL, 0);
			break;
		case 'l':
			if (lnb_decode(optarg, &lnb_type) < 0) {
				bad_usage(argv[0], 1);
				return -1;
			}
			break;
		case 'v':
			verbosity++;
			break;
		case 'q':
			if (--verbosity < 0)
				verbosity = 0;
			break;
		case 'u':
			vdr_dump_channum = 1;
			break;
		case 'P':
			no_ATSC_PSIP = 1;
			break;
		case 'A':
			ATSC_type = strtoul(optarg,NULL,0);
			if (ATSC_type == 0 || ATSC_type > 3) {
				bad_usage(argv[0], 1);
				return -1;
			}

			break;
		case 'U':
			unique_anon_services = 1;
			break;
		case 'C':
			default_charset = optarg;
			break;
		case 'D':
			output_charset = optarg;
			break;
		default:
			bad_usage(argv[0], 0);
			return -1;
		};
	}

	if (optind < argc)
		initial = argv[optind];
	if ((!initial && !current_tp_only) || (initial && current_tp_only) ||
			(spectral_inversion > 2)) {
		bad_usage(argv[0], 0);
		return -1;
	}
	lnb_type.low_val *= 1000;	/* convert to kiloherz */
	lnb_type.high_val *= 1000;	/* convert to kiloherz */
	lnb_type.switch_val *= 1000;	/* convert to kiloherz */
	if (switch_pos >= 4) {
		fprintf (stderr, "switch position needs to be < 4!\n");
		return -1;
	}
	if (initial)
		info("scanning %s\n", initial);

	snprintf (frontend_devname, sizeof(frontend_devname),
		  "/dev/dvb/adapter%i/frontend%i", adapter, frontend);

	snprintf (demux_devname, sizeof(demux_devname),
		  "/dev/dvb/adapter%i/demux%i", adapter, demux);
	info("using '%s' and '%s'\n", frontend_devname, demux_devname);

	for (i = 0; i < MAX_RUNNING; i++)
		poll_fds[i].fd = -1;

	fe_open_mode = current_tp_only ? O_RDONLY : O_RDWR;
	if ((frontend_fd = open (frontend_devname, fe_open_mode)) < 0)
		fatal("failed to open '%s': %d %m\n", frontend_devname, errno);
	/* determine FE type and caps */
	if (ioctl(frontend_fd, FE_GET_INFO, &fe_info) == -1)
		fatal("FE_GET_INFO failed: %d %m\n", errno);

	if ((spectral_inversion == INVERSION_AUTO ) &&
	    !(fe_info.caps & FE_CAN_INVERSION_AUTO)) {
		info("Frontend can not do INVERSION_AUTO, trying INVERSION_OFF instead\n");
		spectral_inversion = INVERSION_OFF;
	}

	signal(SIGINT, handle_sigint);

	if (current_tp_only) {
		current_tp = alloc_transponder(0); /* dummy */
		/* move TP from "new" to "scanned" list */
		list_del_init(&current_tp->list);
		list_add_tail(&current_tp->list, &scanned_transponders);
		current_tp->scan_done = 1;
		scan_tp ();
	}
	else
		scan_network (frontend_fd, initial);

	close (frontend_fd);

	dump_lists ();

	return 0;
}

static void dump_dvb_parameters (FILE *f, struct transponder *t)
{
	switch (output_format) {
		case OUTPUT_PIDS:
		case OUTPUT_VDR:
			vdr_dump_dvb_parameters(f, t->type, &t->param,
					sat_polarisation (t), t->orbital_pos, t->we_flag);
			break;
		case OUTPUT_ZAP:
			zap_dump_dvb_parameters (f, t->type, &t->param,
					sat_polarisation (t), sat_number (t));
			break;
		default:
			break;
	}
}
