tvheadend/teletext.c
2008-08-25 16:22:50 +00:00

374 lines
8.4 KiB
C

/*
* Teletext parsing 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 <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "tvhead.h"
#include "teletext.h"
static void teletext_rundown(th_transport_t *t, channel_t *ch,
tt_page_t *ttp);
#define bitreverse(b) \
(((b) * 0x0202020202ULL & 0x010884422010ULL) % 1023)
static const uint8_t hamtable[] = {
0x01, 0xff, 0x81, 0x01, 0xff, 0x00, 0x01, 0xff,
0xff, 0x02, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x07,
0xff, 0x00, 0x01, 0xff, 0x00, 0x80, 0xff, 0x00,
0x06, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x03, 0xff,
0xff, 0x0c, 0x01, 0xff, 0x04, 0xff, 0xff, 0x07,
0x06, 0xff, 0xff, 0x07, 0xff, 0x07, 0x07, 0x87,
0x06, 0xff, 0xff, 0x05, 0xff, 0x00, 0x0d, 0xff,
0x86, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0x07,
0xff, 0x02, 0x01, 0xff, 0x04, 0xff, 0xff, 0x09,
0x02, 0x82, 0xff, 0x02, 0xff, 0x02, 0x03, 0xff,
0x08, 0xff, 0xff, 0x05, 0xff, 0x00, 0x03, 0xff,
0xff, 0x02, 0x03, 0xff, 0x03, 0xff, 0x83, 0x03,
0x04, 0xff, 0xff, 0x05, 0x84, 0x04, 0x04, 0xff,
0xff, 0x02, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x07,
0xff, 0x05, 0x05, 0x85, 0x04, 0xff, 0xff, 0x05,
0x06, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x03, 0xff,
0xff, 0x0c, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x09,
0x0a, 0xff, 0xff, 0x0b, 0x8a, 0x0a, 0x0a, 0xff,
0x08, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x0d, 0xff,
0xff, 0x0b, 0x0b, 0x8b, 0x0a, 0xff, 0xff, 0x0b,
0x0c, 0x8c, 0xff, 0x0c, 0xff, 0x0c, 0x0d, 0xff,
0xff, 0x0c, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x07,
0xff, 0x0c, 0x0d, 0xff, 0x0d, 0xff, 0x8d, 0x0d,
0x06, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x0d, 0xff,
0x08, 0xff, 0xff, 0x09, 0xff, 0x09, 0x09, 0x89,
0xff, 0x02, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x09,
0x88, 0x08, 0x08, 0xff, 0x08, 0xff, 0xff, 0x09,
0x08, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x03, 0xff,
0xff, 0x0c, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x09,
0x0f, 0xff, 0x8f, 0x0f, 0xff, 0x0e, 0x0f, 0xff,
0x08, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x0d, 0xff,
0xff, 0x0e, 0x0f, 0xff, 0x0e, 0x8e, 0xff, 0x0e,
};
static uint8_t
ham_decode(uint8_t a, uint8_t b)
{
a = hamtable[a];
b = hamtable[b];
return (b << 4) | (a & 0xf);
}
tt_page_t *
tt_get_page(tt_decoder_t *ttd, int page)
{
tt_page_t *p;
if(page >= 900)
return NULL;
p = ttd->pages[page];
if(p == NULL) {
p = malloc(sizeof(tt_page_t));
p->ttp_page = page;
p->ttp_subpage = 0;
p->ttp_ver = 0;
memset(p->ttp_pagebuf, ' ', 23 * 40);
p->ttp_pagebuf[40*23] = 0;
ttd->pages[page] = p;
}
return p;
}
static time_t
tt_construct_unix_time(char *buf)
{
time_t t, r[3], v[3];
int i;
struct tm tm;
time(&t);
localtime_r(&t, &tm);
tm.tm_hour = atoi(buf);
tm.tm_min = atoi(buf + 3);
tm.tm_sec = atoi(buf + 6);
r[0] = mktime(&tm);
tm.tm_mday--;
r[1] = mktime(&tm);
tm.tm_mday += 2;
r[2] = mktime(&tm);
for(i = 0; i < 3; i++)
v[i] = labs(r[i] - t);
if(v[0] < v[1] && v[0] < v[2])
return r[0];
if(v[1] < v[2] && v[1] < v[0])
return r[1];
return r[2];
}
static int
str_is_tt_clock(const char *str)
{
return
isdigit(str[0]) && isdigit(str[1]) && str[2] == ':' &&
isdigit(str[3]) && isdigit(str[4]) && str[5] == ':' &&
isdigit(str[6]) && isdigit(str[7]);
}
static int
update_tt_clock(th_transport_t *t, char *buf)
{
char str[10];
int i;
time_t ti;
for(i = 0; i < 8; i++)
str[i] = buf[i] & 0x7f;
str[8] = 0;
if(!str_is_tt_clock(str))
return 0;
ti = tt_construct_unix_time(str);
if(t->tht_tt_clock == ti)
return 0;
t->tht_tt_clock = ti;
return 1;
}
static void
tt_decode_line(th_transport_t *t, uint8_t *buf)
{
uint8_t mpag, line, s12, s34, c;
int page, magidx, i;
tt_mag_t *mag;
channel_t *ch = t->tht_ch;
tt_decoder_t *ttd = &ch->ch_tt;
tt_page_t *ttp;
if(ch == NULL)
return; /* Not mapped to a channel, do not do anything */
mpag = ham_decode(buf[0], buf[1]);
magidx = mpag & 7;
mag = &ttd->mags[magidx];
line = mpag >> 3;
if(line >= 30)
return; /* Network lines, PDC, etc, dont worry about these */
if(line == 0) {
if(mag->pageptr != NULL)
mag->pageptr->ttp_ver++;
page = ham_decode(buf[2], buf[3]);
if(page == 0xff)
return;
/* The page is BDC encoded, mag 0 is displayed as page 800+ */
page = (magidx ?: 8) * 100 + (page >> 4) * 10 + (page & 0xf);
mag->pageptr = tt_get_page(ttd, page);
if(mag->pageptr == NULL)
return;
s12 = ham_decode(buf[4], buf[5]);
s34 = ham_decode(buf[6], buf[7]);
c = ham_decode(buf[8], buf[9]);
ttd->magazine_serial = c & 0x10 ? 1 : 0;
if(s12 & 0x80) {
/* Erase page */
memset(mag->pageptr->ttp_pagebuf, ' ', 23 * 40);
}
if(update_tt_clock(t, (char *)buf + 34)) {
if(ch->ch_commercial_detection == COMMERCIAL_DETECT_TTP192) {
ttp = tt_get_page(ttd, 192);
teletext_rundown(t, ch, ttp);
}
}
#if 0
printf("%02x: %s\n", s12, buf[9 - 4] & (1 << 7) ? "Erase" : "Not Erase");
printf("Page %d: Control = %x\n", mag->page, c);
printf("%s\n", buf[13 - 4] & (1 << 1) ? "Magser" : "");
printf("%s\n", buf[12 - 4] & (1 << 7) ? "Inhibit" : "");
printf("%s\n", buf[12 - 4] & (1 << 5) ? "Intr" : "");
printf("%s\n", buf[12 - 4] & (1 << 3) ? "Update" : "");
printf("%s\n", buf[12 - 4] & (1 << 1) ? "Supress" : "");
#endif
}
if((ttp = mag->pageptr) != NULL) {
switch(line) {
case 1 ... 23:
for(i = 0; i < 40; i++) {
c = buf[i + 2] & 0x7f;
if(c < 32)
c += 0x80;
ttp->ttp_pagebuf[i + 40 * (line - 1)] = c;
}
break;
}
if(ttp->ttp_page == 192 &&
ch->ch_commercial_detection == COMMERCIAL_DETECT_TTP192)
teletext_rundown(t, ch, ttp);
}
}
void
teletext_input(th_transport_t *t, uint8_t *tsb)
{
int i, j;
uint8_t *x, buf[42];
x = tsb + 4;
for(i = 0; i < 4; i++) {
if(*x == 2) {
for(j = 0; j < 42; j++)
buf[j] = bitreverse(x[4 + j]);
tt_decode_line(t, buf);
}
x += 46;
}
}
/*
*06: | 21:54:44 RV3 KYLTJEJ ST[NGER 00:00:03|
08: | 22:10:40 RV3 KYSS 00:00:03| (0s)
*/
static int
tt_time_to_len(const char *buf)
{
int l;
char str[10];
if(!str_is_tt_clock(buf))
return 0;
memcpy(str, buf, 8);
str[2] = 0;
str[5] = 0;
str[8] = 0;
l = atoi(str + 0) * 3600 + atoi(str + 3) * 60 + atoi(str + 6);
return l;
}
/*
* Decode the Swedish TV4 teletext rundown page to figure out if we are
* currently in a commercial break
*/
static void
teletext_rundown(th_transport_t *t, channel_t *ch, tt_page_t *ttp)
{
char r[50];
int i;
time_t ti, d, l;
int curlen = -1;
th_commercial_advice_t prev;
#if 0
printf("%c", 0x0c);
printf("%-20s %s\n",
t->tht_tt_rundown_content_length > 400 ? "real stuff" : "commercial",
ctime(&t->tht_tt_clock));
#endif
for(i = 0; i < 22; i++) {
memcpy(r, &ttp->ttp_pagebuf[i * 40], 40);
r[40] = 0;
if(!str_is_tt_clock(r + 2)) {
continue;
}
ti = tt_construct_unix_time(r + 2);
l = tt_time_to_len(r + 32);
d = ti - t->tht_tt_clock;
if(d < 1) {
/* Currently playing show */
curlen = l;
}
// printf("%02d|%s|\t(%5lds) : %ld, %d\n", i, r, l, d, curlen);
}
t->tht_tt_rundown_content_length = curlen;
prev = t->tht_tt_commercial_advice;
if(curlen < 0)
return;
if(curlen < 400)
t->tht_tt_commercial_advice = COMMERCIAL_YES;
else
t->tht_tt_commercial_advice = COMMERCIAL_NO;
if(prev != t->tht_tt_commercial_advice) {
tvhlog(LOG_DEBUG, "teletext",
"teletext-rundown: \"%s\" on \"%s\": "
"%s commercial break (chunk %ds)",
ch->ch_name, t->tht_name,
t->tht_tt_commercial_advice == COMMERCIAL_YES ? "In" : "Not in",
curlen);
}
}