/*
* TV Input - DVB - Support functions
* Copyright (C) 2007 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "tvheadend.h"
#include "dvb_support.h"
#include "dvb.h"
#include "dvb_charset_tables.h"
static int convert_iso_8859[16] = {
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 11, 12, 13
};
#define convert_utf8 14
#define convert_iso6937 15
static inline int encode_utf8(unsigned int c, char *outb, int outleft)
{
if (c <= 0x7F && outleft >= 1) {
*outb = c;
return 1;
} else if (c <= 0x7FF && outleft >=2) {
*outb++ = ((c >> 6) & 0x1F) | 0xC0;
*outb++ = ( c & 0x3F) | 0x80;
return 2;
} else if (c <= 0xFFFF && outleft >= 3) {
*outb++ = ((c >> 12) & 0x0F) | 0xE0;
*outb++ = ((c >> 6) & 0x3F) | 0x80;
*outb++ = ( c & 0x3F) | 0x80;
return 3;
} else if (c <= 0x10FFFF && outleft >= 4) {
*outb++ = ((c >> 18) & 0x07) | 0xF0;
*outb++ = ((c >> 12) & 0x3F) | 0x80;
*outb++ = ((c >> 6) & 0x3F) | 0x80;
*outb++ = ( c & 0x3F) | 0x80;
return 4;
} else {
return -1;
}
}
static inline size_t conv_utf8(const uint8_t *src, size_t srclen,
char *dst, size_t *dstlen)
{
while (srclen>0 && (*dstlen)>0) {
*dst = (char) *src;
srclen--; (*dstlen)--;
src++; dst++;
}
if (srclen>0) {
errno = E2BIG;
return -1;
}
return 0;
}
static inline size_t conv_8859(int conv,
const uint8_t *src, size_t srclen,
char *dst, size_t *dstlen)
{
uint16_t *table = conv_8859_table[conv];
while (srclen>0 && (*dstlen)>0) {
uint8_t c = *src;
if (c <= 0x7f) {
// lower half of iso-8859-* is identical to utf-8
*dst = (char) *src;
(*dstlen)--;
dst++;
} else if (c <= 0x9f) {
// codes 0x80 - 0x9f (control codes) are ignored
} else {
// map according to character table, skipping
// unmapped chars (value 0 in the table)
uint16_t uc = table[c-0xa0];
if (uc != 0) {
int len = encode_utf8(uc, dst, *dstlen);
if (len == -1) {
errno = E2BIG;
return -1;
} else {
(*dstlen) -= len;
dst += len;
}
}
}
srclen--;
src++;
}
if (srclen>0) {
errno = E2BIG;
return -1;
}
return 0;
}
static inline size_t conv_6937(const uint8_t *src, size_t srclen,
char *dst, size_t *dstlen)
{
while (srclen>0 && (*dstlen)>0) {
uint8_t c = *src;
if (c <= 0x7f) {
// lower half of iso6937 is identical to utf-8
*dst = (char) *src;
(*dstlen)--;
dst++;
} else if (c <= 0x9f) {
// codes 0x80 - 0x9f (control codes) are mapped to ' '
*dst = ' ';
(*dstlen)--;
dst++;
} else {
uint16_t uc;
if (c >= 0xc0 && c <= 0xcf) {
// map two-byte sequence, skipping illegal combinations.
if (srclen<2) {
errno = EINVAL;
return -1;
}
srclen--;
src++;
uint8_t c2 = *src;
if (c2 == 0x20) {
uc = iso6937_lone_accents[c-0xc0];
} else if (c2 >= 0x41 && c2 <= 0x5a) {
uc = iso6937_multi_byte[c-0xc0][c2-0x41];
} else if (c2 >= 0x61 && c2 <= 0x7a) {
uc = iso6937_multi_byte[c-0xc0][c2-0x61+26];
} else {
uc = 0;
}
} else {
// map according to single character table, skipping
// unmapped chars (value 0 in the table)
uc = iso6937_single_byte[c-0xa0];
}
if (uc != 0) {
int len = encode_utf8(uc, dst, *dstlen);
if (len == -1) {
errno = E2BIG;
return -1;
} else {
(*dstlen) -= len;
dst += len;
}
}
}
srclen--;
src++;
}
if (srclen>0) {
errno = E2BIG;
return -1;
}
return 0;
}
static inline size_t dvb_convert(int conv,
const uint8_t *src, size_t srclen,
char *dst, size_t *dstlen)
{
switch (conv) {
case convert_utf8: return conv_utf8(src, srclen, dst, dstlen);
case convert_iso6937: return conv_6937(src, srclen, dst, dstlen);
default: return conv_8859(conv, src, srclen, dst, dstlen);
}
}
/*
* DVB String conversion according to EN 300 468, Annex A
* Not all character sets are supported, but it should cover most of them
*/
int
dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen, const char *dvb_charset, dvb_string_conv_t *conv)
{
int ic;
size_t len, outlen;
int i, auto_pl_charset = 0;
if(srclen < 1) {
*dst = 0;
return 0;
}
/* Check custom conversion */
while (conv && conv->func) {
if (conv->type == src[0])
return conv->func(dst, &dstlen, src, srclen);
conv++;
}
// check for automatic polish charset detection
if (dvb_charset && strcmp("PL_AUTO", dvb_charset) == 0) {
auto_pl_charset = 1;
dvb_charset = NULL;
}
// automatic charset detection
switch(src[0]) {
case 0:
return -1;
case 0x01 ... 0x0b:
if (auto_pl_charset && (src[0] + 4) == 5)
ic = convert_iso6937;
else
ic = convert_iso_8859[src[0] + 4];
src++; srclen--;
break;
case 0x0c ... 0x0f:
return -1;
case 0x10: /* Table A.4 */
if(srclen < 3 || src[1] != 0 || src[2] == 0 || src[2] > 0x0f)
return -1;
ic = convert_iso_8859[src[2]];
src+=3; srclen-=3;
break;
case 0x11 ... 0x14:
return -1;
case 0x15:
ic = convert_utf8;
break;
case 0x16 ... 0x1f:
return -1;
default:
if (auto_pl_charset)
ic = convert_iso_8859[2];
else
ic = convert_iso6937;
break;
}
// manual charset override
if (dvb_charset != NULL && dvb_charset[0] != 0) {
if (sscanf(dvb_charset, "ISO8859-%d", &i) > 0 && i > 0 && i < 16) {
ic = convert_iso_8859[i];
} else {
ic = convert_iso6937;
}
}
if(srclen < 1) {
*dst = 0;
return 0;
}
if(ic == -1)
return -1;
outlen = dstlen - 1;
if (dvb_convert(ic, src, srclen, dst, &outlen) == -1) {
return -1;
}
len = dstlen - outlen - 1;
dst[len] = 0;
return 0;
}
int
dvb_get_string_with_len(char *dst, size_t dstlen,
const uint8_t *buf, size_t buflen, const char *dvb_charset,
dvb_string_conv_t *conv)
{
int l = buf[0];
if(l + 1 > buflen)
return -1;
if(dvb_get_string(dst, dstlen, buf + 1, l, dvb_charset, conv))
return -1;
return l + 1;
}
/**
*
*/
void
atsc_utf16_to_utf8(uint8_t *src, int len, char *buf, int buflen)
{
int i, c, r;
for(i = 0; i < len; i++) {
c = (src[i * 2 + 0] << 8) | src[i * 2 + 1];
if(buflen >= 7) {
r = put_utf8(buf, c);
buf += r;
buflen -= r;
}
}
*buf = 0;
}
/*
* DVB time and date functions
*/
time_t
dvb_convert_date(uint8_t *dvb_buf)
{
int i;
int year, month, day, hour, min, sec;
long int mjd;
struct tm dvb_time;
mjd = (dvb_buf[0] & 0xff) << 8;
mjd += (dvb_buf[1] & 0xff);
hour = bcdtoint(dvb_buf[2] & 0xff);
min = bcdtoint(dvb_buf[3] & 0xff);
sec = bcdtoint(dvb_buf[4] & 0xff);
/*
* Use the routine specified in ETSI EN 300 468 V1.4.1,
* "Specification for Service Information in Digital Video Broadcasting"
* to convert from Modified Julian Date to Year, Month, Day.
*/
year = (int) ((mjd - 15078.2) / 365.25);
month = (int) ((mjd - 14956.1 - (int) (year * 365.25)) / 30.6001);
day = mjd - 14956 - (int) (year * 365.25) - (int) (month * 30.6001);
if (month == 14 || month == 15)
i = 1;
else
i = 0;
year += i;
month = month - 1 - i * 12;
dvb_time.tm_sec = sec;
dvb_time.tm_min = min;
dvb_time.tm_hour = hour;
dvb_time.tm_mday = day;
dvb_time.tm_mon = month - 1;
dvb_time.tm_year = year;
dvb_time.tm_isdst = -1;
dvb_time.tm_wday = 0;
dvb_time.tm_yday = 0;
return (timegm(&dvb_time));
}
/**
*
*/
static struct strtab adaptertype[] = {
{ "DVB-S", FE_QPSK },
{ "DVB-C", FE_QAM },
{ "DVB-T", FE_OFDM },
{ "ATSC", FE_ATSC },
};
int
dvb_str_to_adaptertype(const char *str)
{
return str2val(str, adaptertype);
}
const char *
dvb_adaptertype_to_str(int type)
{
return val2str(type, adaptertype) ?: "invalid";
}
const char *
dvb_polarisation_to_str(int pol)
{
switch(pol) {
case POLARISATION_VERTICAL: return "V";
case POLARISATION_HORIZONTAL: return "H";
case POLARISATION_CIRCULAR_LEFT: return "L";
case POLARISATION_CIRCULAR_RIGHT: return "R";
default: return "X";
}
}
const char *
dvb_polarisation_to_str_long(int pol)
{
switch(pol) {
case POLARISATION_VERTICAL: return "Vertical";
case POLARISATION_HORIZONTAL: return "Horizontal";
case POLARISATION_CIRCULAR_LEFT: return "Left";
case POLARISATION_CIRCULAR_RIGHT: return "Right";
default: return "??";
}
}
th_dvb_adapter_t *
dvb_adapter_find_by_identifier(const char *identifier)
{
th_dvb_adapter_t *tda;
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link)
if(!strcmp(identifier, tda->tda_identifier))
return tda;
return NULL;
}
/**
*
*/
static const char *
nicenum(char *x, size_t siz, unsigned int v)
{
if(v < 1000)
snprintf(x, siz, "%d", v);
else if(v < 1000000)
snprintf(x, siz, "%d,%03d", v / 1000, v % 1000);
else if(v < 1000000000)
snprintf(x, siz, "%d,%03d,%03d",
v / 1000000, (v % 1000000) / 1000, v % 1000);
else
snprintf(x, siz, "%d,%03d,%03d,%03d",
v / 1000000000, (v % 1000000000) / 1000000,
(v % 1000000) / 1000, v % 1000);
return x;
}
/**
*
*/
void
dvb_mux_nicefreq(char *buf, size_t size, th_dvb_mux_instance_t *tdmi)
{
char freq[50];
if(tdmi->tdmi_adapter->tda_type == FE_QPSK) {
nicenum(freq, sizeof(freq), tdmi->tdmi_conf.dmc_fe_params.frequency);
snprintf(buf, size, "%s kHz", freq);
} else {
nicenum(freq, sizeof(freq),
tdmi->tdmi_conf.dmc_fe_params.frequency / 1000);
snprintf(buf, size, "%s kHz", freq);
}
}
/**
*
*/
void
dvb_mux_nicename(char *buf, size_t size, th_dvb_mux_instance_t *tdmi)
{
char freq[50];
const char *n = tdmi->tdmi_network;
if(tdmi->tdmi_adapter->tda_type == FE_QPSK) {
nicenum(freq, sizeof(freq), tdmi->tdmi_conf.dmc_fe_params.frequency);
snprintf(buf, size, "%s%s%s kHz %s (%s)",
n?:"", n ? ": ":"", freq,
dvb_polarisation_to_str_long(tdmi->tdmi_conf.dmc_polarisation),
tdmi->tdmi_conf.dmc_satconf ? tdmi->tdmi_conf.dmc_satconf->sc_name : "No satconf");
} else {
nicenum(freq, sizeof(freq), tdmi->tdmi_conf.dmc_fe_params.frequency / 1000);
snprintf(buf, size, "%s%s%s kHz", n?:"", n ? ": ":"", freq);
}
}