summaryrefslogtreecommitdiff
path: root/teletext.c
diff options
context:
space:
mode:
authorTobias Grimm <tobias@e-tobi.loc>2008-12-13 10:35:43 +0100
committerTobias Grimm <tobias@e-tobi.loc>2008-12-13 10:35:43 +0100
commit76ac85e366bfc27b3b688a4f13031c0735ea2436 (patch)
tree0f4f27bb366be92faee4e8c05fe01cccb32a9794 /teletext.c
downloadvdr-plugin-ttxtsubs-0.0.1.tar.gz
vdr-plugin-ttxtsubs-0.0.1.tar.bz2
Initial version 0.0.1v0.0.1
Diffstat (limited to 'teletext.c')
-rw-r--r--teletext.c724
1 files changed, 724 insertions, 0 deletions
diff --git a/teletext.c b/teletext.c
new file mode 100644
index 0000000..13ea178
--- /dev/null
+++ b/teletext.c
@@ -0,0 +1,724 @@
+
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <ctype.h>
+#include <string.h>
+
+//#include <linux/dvb/dmx.h>
+//#include <vdr/osd.h>
+
+#include "teletext.h"
+#include "teletext-tables.h"
+#include "teletext-chars.h"
+#include "utils.h"
+
+
+// FROM vbidecode
+// unham 2 bytes into 1, report 2 bit errors but ignore them
+unsigned char unham(unsigned char a,unsigned char b)
+{
+ unsigned char c1,c2;
+
+ c1=unhamtab[a];
+ c2=unhamtab[b];
+// if ((c1|c2)&0x40)
+// fprintf(stderr,"bad ham!");
+ return (c2<<4)|(0x0f&c1);
+}
+
+
+// ham 8/4 the byte in into two bytes pointed to by out
+// should make a table instead
+void ham8_4byte(uint8_t in, uint8_t *out)
+{
+ out[0] = ham8_4nibble(in & 0xF);
+ out[1] = ham8_4nibble(in >> 4);
+
+ if (1) { // XXX debug
+ int a;
+ if(unham(out[0], out[1]) != in) {
+ fprintf(stderr, "ham8_4: 1 - result not correct %02x -> %02x %02x!\n", in, out[0], out[1]);
+ }
+ a = unhamtab[out[0]];
+ a ^= 0x80;
+ if(a & 0xf0 || (a != (in & 0xF))) {
+ fprintf(stderr, "ham8_4: 2 - result not correct %02x -> %02x %02x, %02x!\n", in, out[0], out[1], a);
+ }
+ a = unhamtab[out[1]];
+ a ^= 0x80;
+ if(a & 0xf0 || (a != (in >> 4))) {
+ fprintf(stderr, "ham8_4: 3 - result not correct %02x -> %02x %02x, %02x!\n", in, out[0], out[1], a);
+ }
+ }
+}
+
+// should be a table instead
+int parity(uint8_t x) {
+ int res = 1;
+ int count = x & 0xf0 ? 8 : 4;
+
+ while(count--) {
+ if(x & 0x01)
+ res = !res;
+ x >>= 1;
+ }
+
+ return res;
+}
+
+// ham 8/4 the nibble in into the byte pointed to by out
+// should make a table instead
+// D4 P4 D3 P3 D2 P2 D1 P1
+// P4 = all the rest
+uint8_t ham8_4nibble(uint8_t in)
+{
+ uint8_t o = 0;
+
+ // insert the data bits
+ o |= (in << 4) & 0x80;
+ o |= (in << 3) & 0x20;
+ o |= (in << 2) & 0x08;
+ o |= (in << 1) & 0x02;
+
+ if(parity(in & 0x0d)) // P1 = 1 . D1 . D3 . D4
+ o |= 0x01;
+ if(parity(in & 0x0b)) // P2 = 1 . D1 . D2 . D4
+ o |= 0x04;
+ if(parity(in & 0x07)) // P3 = 1 . D1 . D2 . D3
+ o |= 0x10;
+ if(parity(o & 0xbf)) // P4 = 1 . P1 . D1 . ... . D3 . D4
+ o |= 0x40;
+
+ return o;
+}
+
+
+/*
+ * Map Latin G0 teletext characters into a ISO-8859-1 approximation.
+ * Trying to use similar looking or similar meaning characters.
+ * Gtriplet - 4 bits = triplet-1 bits 14-11 (14-8) of a X/28 or M/29 packet, if unknown - use 0
+ * natopts - 3 bits = national_options field of a Y/0 packet (or triplet-1 bits 10-8 as above?)
+ * inchar - 7 bits = characted to remap
+ * Also strips parity
+ */
+
+uint8_t ttxt_laG0_la1_char(int Gtriplet, int natopts, uint8_t inchar)
+{
+ int no = laG0_nat_opts_lookup[Gtriplet & 0xf][natopts & 0x7];
+ uint8_t c = inchar & 0x7f;
+
+ //dprint("\n%x/%c/%x/%x\n", c, c, laG0_nat_replace_map[c], laG0_nat_opts[no][laG0_nat_replace_map[c]]);
+
+ if(!laG0_nat_replace_map[c])
+ return c;
+ else
+ return laG0_nat_opts[no][laG0_nat_replace_map[c]];
+}
+
+/*
+ * Map Latin G2 teletext characters into a ISO-8859-1 approximation.
+ * Trying to use similar looking or similar meaning characters.
+ */
+uint8_t ttxt_laG2_la1_char(uint8_t inchar)
+{
+ return laG2_la1_table[inchar & 0x7f];
+}
+
+void dump_hex(char *msg, const uint8_t *p, int len)
+{
+ int i;
+ printf("%s:", msg);
+ for(i = 0; i < len; i++) {
+ printf(" %02x", p[i]);
+ }
+ printf("\n");
+}
+
+
+void
+print_code(uint8_t c, int natopts)
+{
+ if(c >= 0x20) {
+ dprint("%c", ttxt_laG0_la1_char(0, natopts, c));
+ } else
+ switch(c) {
+ case 0x0:
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ case 0x4:
+ case 0x5:
+ case 0x6:
+ case 0x7:
+ dprint("<fc%d>", c);
+ break;
+ case 0x08:
+ dprint("<flash>");
+ break;
+ case 0x09:
+ dprint("<Steady>");
+ break;
+ case 0x0a:
+ dprint("]");
+ break;
+ case 0x0b:
+ dprint("[");
+ break;
+ case 0x0c:
+ dprint("<normal size>");
+ break;
+ case 0x0d:
+ dprint("<dh>");
+ break;
+ case 0x0e:
+ dprint("<dbl width>");
+ break;
+ case 0x0f:
+ dprint("<dbl size>");
+ break;
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x15:
+ case 0x16:
+ case 0x17:
+ dprint("<mosc%d>", c-0x10);
+ break;
+ case 0x18:
+ dprint("<conceal>");
+ break;
+ case 0x19:
+ dprint("<cont mosaic>");
+ break;
+ case 0x1a:
+ dprint("<sep mosaic>");
+ break;
+ case 0x1b:
+ dprint("<charset switch>");
+ break;
+ case 0x1c:
+ dprint("<black bg>");
+ break;
+ case 0x1d:
+ dprint("<new bg>");
+ break;
+ case 0x1e:
+ dprint("<hold mosaic>");
+ break;
+ case 0x1f:
+ dprint("<rele mosaic>");
+ break;
+ default:
+ dprint("<U%02x>", c);
+ }
+}
+
+void
+print_page(struct ttxt_page *p)
+{
+ int r, c, e;
+
+ dprint("\n\n%03x %s%s%s%s%snatchars: %d\n",
+ (p->mag << 8) + p->no,
+ p->flags & erasepage ? "erasepage, " : "",
+ p->flags & newsflash ? "Newsflash, " : "",
+ p->flags & subtitle ? "Subtitle, " : "",
+ p->flags & suppress_header ? "SuppressHeader, " : "",
+ p->flags & inhibit_display ? "InihinitDisplay, " : "",
+ p->national_charset);
+
+ for(r = 0; r < 26; r++) {
+ int do_display = 0;
+
+ for(e = 39; e > 0; e--) {
+ if(p->data[r][e]) {
+ do_display = 1;
+ break;
+ }
+ }
+
+ if(do_display) {
+ dprint("%02d ", r);
+ for(c = 0; c <= e; c++) {
+ char z = p->data[r][c] & 0x7f;
+ print_code(z, p->national_charset);
+ //dprint("%c", isprint(z) ? z : z == 0 ? '\'' : '.');
+ }
+ dprint("\n");
+ }
+ }
+}
+
+
+void print_line(char *p)
+{
+ int mp;
+ int mag; // X in ETSI EN 300 706
+ int packet; // Y
+ struct ttxt_data_field *d = (struct ttxt_data_field *) p;
+
+ mp = unham(invtab[d->mag_addr_ham[0]], invtab[d->mag_addr_ham[1]]);
+ mag = mp & 0x7;
+ packet = (mp >> 3) & 0x1f;
+
+ dprint("I%d Y%02d M%d ", d->data_unit_id, packet, mag);
+
+ // packet 0, change page no in this magazine
+ if(packet == 0) {
+ int no;
+ uint8_t buf[8];
+
+ for(int i = 0; i < 8; i++)
+ buf[i] = invtab[d->data[i]];
+
+ no = unham(buf[0], buf[1]);
+
+ dprint("P%02x ", no);
+
+ dprint("<");
+ for(int i = 8; i < 40; i++)
+ print_code(invtab[d->data[i]] & 0x7f, 0);
+ dprint(">");
+
+ if(buf[3] & 0x80) dprint(", erase page");
+ if(buf[5] & 0x20) dprint(", newsflash");
+ if(buf[5] & 0x80) dprint(", subtitle");
+ if(buf[6] & 0x02) dprint(", suppress header");
+ if(buf[6] & 0x08) dprint(", update indicator");
+ if(buf[6] & 0x20) dprint(", interrupted sequence");
+ if(buf[6] & 0x80) dprint(", inhibit display");
+ if(buf[7] & 0x02) dprint(", magazine serial");
+
+ dprint(", charset=%d", ((buf[7] & 0x80) >> 5) + ((buf[7] & 0x20) >> 4) + ((buf[7] & 0x08) >> 3));
+ } else {
+ dprint("<");
+ for(int i = 0; i < 40; i++)
+ print_code(invtab[d->data[i]] & 0x7f, 0);
+ dprint(">");
+ }
+
+ dprint("\n");
+}
+
+
+#if 0
+void
+show_page(struct ttxt_page *p)
+{
+ static cOsdBase *o = NULL;
+ static tWindowHandle w1 = 0;
+ static tWindowHandle w2 = 0;
+ static int open1 = 0, open2 = 0;
+ char *s1 = NULL, *s2 = NULL;
+ int i, j;
+
+ for(i = 1; i < TTXT_ROWS; i++) {
+ char *s = NULL;
+ if(p->data[i][0]) {
+ int do_incr = 0;
+ dprint("Checking row %d\n", i);
+ for(j = 0; j < 40; j++) {
+ p->data[i][j] = p->data[i][j] & 0x7f;
+ switch(p->data[i][j]) {
+ case 0x0d: // double height
+ case 0x0f: // double size
+ do_incr = 1;
+ break;
+ case 0x0a: // box end
+ p->data[i][j] = '\0';
+ break;
+ case 0x0b: // box begin
+ s = (char *) &(p->data[i][j+1]);
+ dprint("Using row %d\n", i);
+ break;
+ case '[':
+ p->data[i][j] = 'Ä';
+ break;
+ case ']':
+ p->data[i][j] = 'Å';
+ break;
+ case '\\':
+ p->data[i][j] = 'Ö';
+ break;
+ case '{':
+ p->data[i][j] = 'ä';
+ break;
+ case '}':
+ p->data[i][j] = 'å';
+ break;
+ case '|':
+ p->data[i][j] = 'ö';
+ break;
+ }
+ }
+ if(do_incr)
+ i++;
+ dprint("\n");
+ }
+
+ if(s)
+ if(!s1)
+ s1 = s;
+ else if(!s2)
+ s2 = s;
+ }
+
+ if(s1 && !s2) {
+ s2 = s1;
+ s1 = NULL;
+ }
+
+ if(s1)
+ dprint("S1: %s\n", s1);
+ else
+ dprint("S1: <none>\n");
+ if(s2)
+ dprint("S2: %s\n", s2);
+ else
+ dprint("S2: <none>\n");
+
+ if(o != NULL) {
+ if(open1) {
+ o->Clear(w1);
+ open1 = 0;
+ }
+ if(open2) {
+ o->Clear(w2);
+ open2 = 0;
+ }
+
+ o->Clear();
+ delete o;
+ o = NULL;
+ }
+
+ if(!s1 && !s2)
+ return;
+
+ o = cOsd::OpenRaw(125, 450);
+
+ if(o == NULL)
+ return;
+
+ o->SetFont(fontOsd);
+
+ if(s1) {
+ int w = o->Width((char *) s1);
+ dprint("w1: %d\n", w);
+ w = 450;
+ int h = 36;
+ w1 = o->Create(0, 0, w, h, 2);
+#if 0
+ o->AddColor(clrBackground, w1);
+ o->AddColor(clrWhite, w1);
+#endif
+ o->Fill(0, 0, w, h, clrBackground, w1);
+ o->Text(15, 4, (char *) s1, clrWhite, clrBackground, w1);
+ open1 = 1;
+ }
+
+ if(s2) {
+ int w = o->Width((char *) s2);
+ dprint("w2: %d\n", w);
+ w = 452;
+ int h = 36;
+ w2 = o->Create(0, 45, w, h, 2);
+#if 0
+ o->AddColor(clrBackground, w2);
+ o->AddColor(clrWhite, w2);
+#endif
+ o->Fill(0, 45, w, 45 + h, clrBackground, w2);
+ o->Text(15, 45 + 4, (char *) s2, clrWhite, clrBackground, w2);
+ open2 = 1;
+ }
+
+ o->Flush();
+}
+#endif
+
+#if 0
+void
+ttxt_packet_in(int data_unit_id, int mag, int pack, uint8_t *data)
+{
+ static int valid = 0;
+ static struct ttxt_page p;
+ int i;
+
+ if(pack == 0) {
+ if(valid)
+ if(p.mag == TTXT_MAG && p.no == TTXT_PAGE)
+ if(p.subtitle)
+ //print_page(&p);
+ /* if(p.mag == 1) */{
+ show_page(&p);
+ print_page(&p);
+ }
+
+ memset(&p, 0, sizeof(p));
+ p.data_unit_id = data_unit_id;
+ p.mag = mag;
+ p.no = unham(data[0], data[1]);
+
+ if(data[3] & 0x80) { // Erase Page
+ memset(p.data, 0, sizeof(p.data));
+ p.erasepage = 1;
+ }
+ if(data[5] & 0x20) // Newsflash
+ p.newsflash = 1;
+ if(data[5] & 0x80) // Subtitle
+ p.subtitle = 1;
+ if(data[6] & 0x02) // Suppress Header
+ p.suppress_header = 1;
+ //if(data[6] & 0x08) // Update Indicator
+ //if(data[6] & 0x20) // Interrupted Sequence
+ if(data[6] & 0x80) // Inhibit Display
+ p.inhibit_display = 1;
+ //if(data[7] & 0x02) // Magazine Serial
+
+ p.national_charset =
+ ((data[7] & 0x80) >> 5) + ((data[7] & 0x20) >> 4) + ((data[7] & 0x08) >> 3);
+
+ valid = 1;
+
+ for(i = 0; i < 32; i++)
+ p.data[0][i] = data[i+8];
+ } else if(pack < 26) {
+ for(i = 0; i < 42; i++)
+ p.data[pack][i] = data[i];
+ } else {
+ if((valid && p.mag == TTXT_MAG && p.no == TTXT_PAGE) ||
+ pack == 28)
+ dprint("mag: %d, pack: %d, page: %02x\n", p.mag, pack, p.no);
+ }
+}
+#endif
+
+#if 0
+void
+ttxt_pes_data_in(struct ttxt_pes_data_field *t, int len)
+{
+ int i;
+
+ if(len < 1)
+ return;
+
+ if(t->data_identifier < 0x10 || t->data_identifier > 0x1F) {
+ dprint("Unknown data identifier: %02x\n", t->data_identifier);
+ return;
+ }
+
+ for(i = 0; (uint8_t *) (&(t->d[i+1])) < (((uint8_t *) t) + len); i++) {
+ struct ttxt_data_field *d = &(t->d[i]);
+ int i;
+ uint8_t *p;
+ int uh;
+ int mag;
+ int pack;
+
+ // if(d->data_unit_id != ttxt_subtitle)
+ // continue;
+
+ p = (uint8_t *) &(d->mag_addr_ham);
+ for(i = 0; i < 42; i++) {
+ p[i] = invtab[p[i]];
+ }
+
+ uh = unham(d->mag_addr_ham[0], d->mag_addr_ham[1]);
+ mag = uh & 0x7;
+ pack = (uh >> 3) & 0x1f;
+
+ // dump_hex("line", (uint8_t *) d, 20);
+ // dprint("id: %d, line: %d, mag %d, pack: %d\n", d->data_unit_id, TTXT_LINE_NO(*d), mag, pack);
+
+ ttxt_packet_in(d->data_unit_id, mag, pack, d->data);
+ }
+}
+
+
+void
+set_filt(int fd,uint16_t tt_pid, dmx_pes_type_t t)
+{
+ struct dmx_pes_filter_params pesFilterParams;
+
+ memset(&pesFilterParams, 0, sizeof(pesFilterParams));
+
+ pesFilterParams.pid = tt_pid;
+ pesFilterParams.input = DMX_IN_FRONTEND;
+ pesFilterParams.output = DMX_OUT_TAP;
+ pesFilterParams.pes_type = t;
+ pesFilterParams.flags = DMX_IMMEDIATE_START;
+
+ if (ioctl(fd, DMX_SET_PES_FILTER, &pesFilterParams) < 0)
+ perror("DMX SET PES FILTER:");
+
+ // XXX set buffer size too?
+}
+#endif
+
+#if 0
+#define BUFSIZE 2000
+
+// external entry point
+void ttxt_decode(int fd, int tpid, int *doRun)
+{
+ uint8_t buf[BUFSIZE];
+ int n;
+ int hunt = 1;
+
+ set_filt(fd, tpid, DMX_PES_OTHER);
+
+ while (*doRun) {
+ restart:
+ if(hunt) {
+ int i;
+
+ dprint("hunting...\n");
+ n = read(fd, buf, 199);
+ if(n < 0) {
+ perror("ttxt_decode:read: ");
+ goto restart;
+ }
+
+ for(i = 0; i < (n - 3); i++) {
+ if(buf[i] == 0 && buf[i+1] == 0 && buf[i+2] == 1 && buf[i+3] == 0xbd) {
+ struct ttxt_pes_header *p = (struct ttxt_pes_header *) (buf + i);
+ size_t skip = ntohs(p->len) + 6 - (n - i);
+
+ if(skip > 0) {
+ read(fd, buf, skip < sizeof(buf) ? skip : sizeof(buf));
+ if(n < 0) {
+ perror("ttxt_decode:read: ");
+ goto restart;
+ }
+ hunt = 0;
+ break;
+ }
+ }
+ }
+ } else {
+ struct ttxt_pes_header *p = (struct ttxt_pes_header *) buf;
+
+ n = read(fd, buf, 6);
+ if(n < 0) {
+ perror("ttxt_decode:read: ");
+ goto restart;
+ }
+
+ if(p->start[0] != 0 || p->start[1] != 0 || p->start[2] != 1 ||
+ p->stream_id != 0xbd || // private_stream_1
+ ntohs(p->len) > (sizeof(buf) - 6)) {
+ dprint("Lost PES packet sync, hunting...\n");
+ hunt = 1;
+ continue;
+ }
+
+ n = read(fd, buf + 6, ntohs(p->len));
+ if(n < 0) {
+ perror("ttxt_decode:read: ");
+ goto restart;
+ }
+
+ // dprint("len: %d\n", n);
+
+ if(n != ntohs(p->len) ||
+ p->header_len != 0x24) { // header should allways be 0x24 long (EN 300 472)
+ dprint("Error reading PES packet, hunting...\n");
+ hunt = 1;
+ continue;
+ }
+
+ ttxt_pes_data_in((struct ttxt_pes_data_field *) (buf + 45), n + 6 - 45);
+ }
+
+ // fwrite(buf,1,n,stdout);
+ }
+
+ dprint("ttxt_decode DONE!\n");
+}
+#endif
+
+
+#if 0
+int
+main(int argc, char **argv)
+{
+ int fd;
+ int pid;
+ uint8_t buf[BUFSIZE];
+ int n;
+ int hunt = 1;
+
+ if (argc!=2) {
+ fprintf(stderr,"USAGE: %s PID\n", argv[0]);
+ return -1;
+ }
+
+ pid = atoi(argv[1]);
+
+ if((fd = open("/dev/dvb/adapter0/demux0", O_RDWR)) < 0){
+ perror("Error opening demux device: ");
+ return -1;
+ }
+
+ set_filt(fd, pid, DMX_PES_OTHER);
+
+ for (;;) {
+ if(hunt) {
+ int i;
+
+ dprint("hunting...\n");
+ n = read(fd, buf, 199);
+
+ for(i = 0; i < (n - 3); i++) {
+ if(buf[i] == 0 && buf[i+1] == 0 && buf[i+2] == 1 && buf[i+3] == 0xbd) {
+ struct ttxt_pes_header *p = (struct ttxt_pes_header *) (buf + i);
+ int skip = ntohs(p->len) + 6 - (n - i);
+
+ if(skip > 0) {
+ read(fd, buf, skip < sizeof(buf) ? skip : sizeof(buf));
+ hunt = 0;
+ break;
+ }
+ }
+ }
+ } else {
+ struct ttxt_pes_header *p = (struct ttxt_pes_header *) buf;
+
+ n = read(fd, buf, 6);
+
+ if(p->start[0] != 0 || p->start[1] != 0 || p->start[2] != 1 ||
+ p->stream_id != 0xbd || // private_stream_1
+ ntohs(p->len) > (sizeof(buf) - 6)) {
+ dprint("Lost PES packet sync, hunting...\n");
+ hunt = 1;
+ continue;
+ }
+
+ n = read(fd, buf + 6, ntohs(p->len));
+
+ // dprint("len: %d\n", n);
+
+ if(n != ntohs(p->len) ||
+ p->header_len != 0x24) { // header should allways be 0x24 long (EN 300 472)
+ dprint("Error reading PES packet, hunting...\n");
+ hunt = 1;
+ continue;
+ }
+
+ ttxt_pes_data_in((struct ttxt_pes_data_field *) (buf + 45), n + 6 - 45);
+ }
+
+ // fwrite(buf,1,n,stdout);
+ }
+
+ return 0;
+}
+#endif