#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include #include #include #include "../gppport.h" /* loosely based on the program vbidecode.cc by Ralph Metzler */ typedef unsigned int UI; typedef unsigned char u8; typedef U16 u16; /* calculates odd parity, medium-efficient */ static int parodd(U32 data) { u8 p4[16] = { 0,1,1,0, 1,0,0,1, 1,0,0,1, 0,1,1,0 }; int parity = 1; do { parity ^= p4[data & 15]; data >>= 4; } while (data); return parity; } static u8 invtab[256] = { 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, }; static u8 unhamtab[256] = { 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, }; #define VBI_BPL 2048 #define FREQ_PAL 35.468950 #define FREQ_NTSC 28.636363 #define FREQ FREQ_PAL #define FREQ_VT_PAL 6.9375 #define FREQ_VT_NTSC 5.72725 #define FREQ_VT FREQ_VT_PAL /* Replace by FREQ_VT_NTSC and pray that it works */ /*#define FREQ_VT 6.165*/ /* teletext-like signal on france, 0xe7 instead of 0x27 */ #define FREQ_CRYPT 4.5 /* found on premiere */ #define FREQ_VPS 2.5 /* probably only pal */ #define FREQ_VDAT 2.0 /* videodat */ #define FREQ_VC 0.77 /* videocrypt, just ignore */ #define VBI_VT 0x0001 #define VBI_VPS 0x0002 #define VBI_VDAT 0x0004 #define VBI_VC 0x0008 #define VBI_OTHER 0x0010 #define VBI_EMPTY 0x8000 typedef long FP; #define FP_BITS 16 #define FP_0_5 D2FP(0.5) #define D2FP(d) ((FP)((d) * (1<> FP_BITS) #define VT_COLS 40 #define VT_LINES 36 typedef struct { UI types; /* required types */ int offset; /* signal offset */ int did_agc : 1; /* did we already do agc this frame? */ int y; /* the line number */ u8 *line; /* the current line */ FP step; /* the bit step */ FP pos; /* position */ } decoder; static void decoder_init (decoder *dec, UI types) { dec->types = types; dec->did_agc = 0; } static void decoder_scan_start (decoder *dec, UI a, UI b) { u8 *p = dec->line + a; UI med = 128 - dec->offset; do { if (*p >= med) break; } while (++p < dec->line + b); /* find maximum */ while (p[1] > p[0]) p++; dec->pos = I2FP (p - dec->line); } static u8 get_byte (decoder *dec) { u8 byte; int bit = 8; /* if the next bit is a one bit, try to re-center the decoder on it */ if ((dec->offset + dec->line[FP2I(dec->pos)]) & 0x80) { /*if (dec->line[FP2I(dec->pos)] < dec->line[FP2I(dec->pos)+1]) dec->pos += I2FP(1);*/ /* why is this casuing checksum errors? */ /*if (dec->line[FP2I(dec->pos)] < dec->line[FP2I(dec->pos)-1]) dec->pos -= I2FP(1);*/ } byte=0; do { byte >>= 1; byte |= ((dec->offset + dec->line[FP2I(dec->pos)]) & 0x80); dec->pos += dec->step; } while (--bit); return byte; } /* get shift-encoded byte */ static u8 get_byte_SE (decoder *dec) { u8 byte; int bit = 8; do { byte >>= 1; byte |= (dec->line[FP2I(dec->pos)] > dec->line[FP2I(dec->pos + dec->step/2)]) << 7; dec->pos += dec->step; } while (--bit); /* if the next bit is a one bit, try to re-center the decoder on it */ if (dec->line[FP2I(dec->pos)] > 128-dec->offset) { if (dec->line[FP2I(dec->pos)] > dec->line[FP2I(dec->pos)+1]) dec->pos += I2FP(1); if (dec->line[FP2I(dec->pos)] < dec->line[FP2I(dec->pos)-1]) dec->pos -= I2FP(1); } return byte; } static u8 unham4(u8 a) { return unhamtab[a] & 15; } static u8 unham8(u8 a, u8 b) { u8 c1 = unhamtab[a]; u8 c2 = unhamtab[b]; if ( (c1|c2) & 0x40) /* 2 bit error */; return (c1 & 15) | (c2 << 4); } static u16 unham16(u8 a, u8 b, u8 c) { U32 d = (((c << 8) | b) << 8) | c; int A = parodd (d & 0x555555); int B = parodd (d & 0x666666); int C = parodd (d & 0x787878); int D = parodd (d & 0x007f80); int E = parodd (d & 0x7f8000); int F = parodd (d & 0xffffff); int bit; d = (((d >> 16) & 0x7f) << 11) | (((d >> 8) & 0x7f) << 4) | (((d >> 4) & 0x07) << 1) | (((d >> 2) & 0x01) ); if (A&B&C&D&E) return d; if (F) return -1; /* double bit error */ /* correct the single bit error */ return d ^ (1 << (31 - 16*E + 8*D + 4*C + 2*B + A)); } #define rev(byte) (invtab [(u8)(byte)]) static SV * decode_vps (u8 *data) { AV *av = newAV (); char name = rev (data[3]); av_push (av, newSViv (VBI_VPS)); av_push (av, newSVpvn (&name, 1)); av_push (av, newSViv (data[4] & 3)); /* "unknown", "stereo ", "mono ", "dual " */ /* ch, day, mon, hour, min */ av_push (av, newSViv (data[ 4] <<12 & 0xf000 | data[10] & 0x00c0 | data[12] <<10 & 0x0c00 | data[13] << 2 & 0x0300 | data[13] & 0x003f)); av_push (av, newSViv (rev (data[10]) >> 1 & 31)); av_push (av, newSViv (rev (data[10]) << 3 & 8 | rev (data[11]) >> 5)); av_push (av, newSViv (rev (data[11]) & 31)); av_push (av, newSViv (rev (data[12]) >> 2)); av_push (av, newSViv (rev (data[14]))); return newRV_noinc ((SV*)av); } static SV * decode_vt (u8 *data) { AV *av = newAV (); u8 mpag = unham8 (data[3], data[4]); u8 mag = mpag & 7; u8 pack = (mpag & 0xf8) >> 3; av_push (av, newSViv (VBI_VT)); av_push (av, newSViv (mag)); av_push (av, newSViv (pack)); switch (pack) { /* ets300-706 */ case 0: av_push (av, newSVpvn (data+5, 40)); av_push (av, newSViv (unham8 (data[5], data[6]) | (mag << 8))); av_push (av, newSViv (unham8 (data[7], data[8]) | (unham8 (data[9], data[10]) << 8) | (unham8 (data[11], data[12]) << 16))); break; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25: av_push (av, newSVpvn (data+5, 40)); break; case 26: case 27: case 28: case 29: /* enhancement packets */ av_push (av, newSViv (unham4 (data[5]))); av_push (av, newSViv (unham16 (data[ 6], data[ 7], data[ 8]))); av_push (av, newSViv (unham16 (data[ 9], data[10], data[11]))); av_push (av, newSViv (unham16 (data[12], data[13], data[14]))); av_push (av, newSViv (unham16 (data[15], data[16], data[17]))); av_push (av, newSViv (unham16 (data[18], data[19], data[20]))); av_push (av, newSViv (unham16 (data[21], data[22], data[23]))); av_push (av, newSViv (unham16 (data[24], data[25], data[26]))); av_push (av, newSViv (unham16 (data[27], data[28], data[29]))); av_push (av, newSViv (unham16 (data[30], data[31], data[32]))); av_push (av, newSViv (unham16 (data[33], data[34], data[35]))); av_push (av, newSViv (unham16 (data[36], data[37], data[38]))); av_push (av, newSViv (unham16 (data[39], data[40], data[41]))); av_push (av, newSViv (unham16 (data[42], data[43], data[44]))); break; /* ets300-706 & ets300-231 */ case 30: { UI dc = unham4 (data[5]); av_push (av, newSViv (dc)); if ((dc >> 1) == 0) { av_push (av, newSViv (unham8 (data[6], data[7]) | (mag << 8))); av_push (av, newSViv (unham8 (data[8], data[9]) | unham8 (data[10], data[11]) << 8)); av_push (av, newSViv (rev (data[12]) << 8 | rev (data[13]))); } else if ((dc >> 1) == 8) { /* pdc */ } } break; case 31: { UI ft = unham4 (data[5]); UI al = unham4 (data[6]); UI i, addr = 0; /* ets300-708, IDL */ /* http://sunsite.cnlab-switch.ch/ftp/mirror/internet-drafts/draft-ietf-ipvbi-tv-signal-00.txt */ for(i=0; ipos; u8 data[6]; av_push (av, newSViv (VBI_OTHER)); dec->step = D2FP (FREQ / FREQ_CRYPT); /* found on premiere */ data [0] = get_byte (dec); data [1] = get_byte (dec); data [2] = get_byte (dec); if (data[0] == 0x55 && data[1] == 0xd0 && data[2] == 0x18) { /* premiere */ av_push (av, newSViv (1)); } else av_push (av, newSViv (0)); return newRV_noinc ((SV*)av); } static SV * decode_empty () { AV *av = newAV (); av_push (av, newSViv (VBI_EMPTY)); return newRV_noinc ((SV*)av); } static SV * decoder_decode (decoder *dec, UI y, u8 *line) { UI type; u8 data[45]; /* 45 bytes per line are max. */ UI i; int did_agc; type = VBI_VT | VBI_EMPTY | VBI_OTHER | VBI_VC; /* can be everywhere */ if (y == 16 - 7) type |= VBI_VPS; if (y > 17 - 7) type |= VBI_VDAT; type &= dec->types; /* don't do anything unless we need to */ if (type) { dec->line = line; dec->y = y; dec->pos = 0; did_agc = dec->did_agc; /* maybe do agc? */ if (!dec->did_agc || y == 20-7) { u8 *n = line + 120; u8 max = 0, min = 255; do { if (*n > max) max = *n; if (*n < min) min = *n; } while (++n < line + 300); if (max > min + 30) { dec->offset = 128 - (((int)max + (int)min) >> 1); dec->did_agc = 1; } } if (dec->did_agc) { if (type & VBI_VT) { dec->step = D2FP (FREQ / FREQ_VT); decoder_scan_start (dec, 50, 350); data[0] = get_byte (dec); if ((data[0] & 0xfe) == 0x54) { data[1] = get_byte (dec); switch (data[1]) { case 0x27: dec->pos -= dec->step * 2; case 0x4e: dec->pos -= dec->step * 2; case 0x9d: dec->pos -= dec->step * 2; case 0x75: dec->pos -= dec->step * 2; case 0xd5: dec->pos -= dec->step * 2; data[1] = 0x55; case 0x55: break; default: ; /* no teletext page */ } if (data[1] == 0x55) { data[2] = get_byte (dec); if (data[2] == 0x27 || data[2] == 0xe7) { for (i = 3; i < 45; i++) data[i] = get_byte (dec); return decode_vt (data); } } } } if (type & VBI_VPS) { decoder_scan_start (dec, 150, 260); dec->step = D2FP (FREQ / FREQ_VPS); /* shift encoding, two "pixels"/bit (skip the 2nd) */ data[0] = get_byte_SE (dec); if (data[0] == 0xff) { data[1] = get_byte_SE (dec); if (data[1] == 0x5d || data[1] == 0x5f) { for (i = 2; i < 16; i++) data[i] = get_byte_SE (dec); return decode_vps (data); } } } #if 0 if (type & VBI_VDAT) { dec->step = D2FP (FREQ / FREQ_VDAT); decoder_scan_start (dec, 150, 200); } if (type & VBI_VC) { dec->step = D2FP (FREQ / FREQ_VC); decoder_scan_start (dec, 150, 200); } #endif /* watch out for empty lines, test signals etc.. */ if (type & VBI_OTHER) { dec->did_agc = did_agc; /* other signals do not affect agc, yet */ dec->step = D2FP (FREQ); decoder_scan_start (dec, 100, 500); if (dec->pos < I2FP (450)) return decoder_decode_other (dec); } } dec->did_agc = did_agc; if (type & VBI_EMPTY) return decode_empty (); } return 0; } /* vtx decoding routines taken from videotext-0.6.971023, * Copyright (c) 1994-96 Martin Buck */ #define VTX_COLMASK 0x07 #define VTX_BGMASK (0x07 << 3) #define VTX_G1 (1 << 6) #define VTX_GRSEP (1 << 8) #define VTX_HIDDEN (1 << 9) #define VTX_BOX (1 << 10) #define VTX_FLASH (1 << 11) #define VTX_DOUBLE1 (1 << 12) #define VTX_DOUBLE2 (1 << 13) #define VTX_INVERT (1 << 14) #define VTX_DOUBLE (VTX_DOUBLE1 | VTX_DOUBLE2) static const u8 g0_to_iso_table[256] = " " " !\"#$%&'()*+,-./0123456789:;<=>?" "@ABCDEFGHIJKLMNOPQRSTUVWXYZAOU^#" "-abcdefghijklmnopqrstuvwxyzaous#" " " " " " " " "; /* one-to-one copy */ static int decode_vtpage (u8 *src, UI lines, u8 *chr, u16 *attr) { UI line, col, pos, graphics, grhold, doubleht, nextattr = 0; u16 *lastattr, default_attrib = 7, next_attrib; u8 c, *lastchr, default_chr = ' '; UI lang; lang = 4; pos = 0; doubleht = 0; for (line = 0; line < lines; line++) { lastchr = &default_chr; lastattr = &default_attrib; graphics = grhold = 0; if (doubleht && pos > 40) { for (col = 0; col <= 39; col++) { if (attr[pos - 40] & VTX_DOUBLE1) { chr[pos] = chr[pos - 40]; chr[pos - 40] = ' '; attr[pos] = (attr[pos - 40] & ~VTX_DOUBLE1) | VTX_DOUBLE2; } else { chr[pos] = ' '; attr[pos] = attr[pos - 40]; } pos++; } doubleht = 0; } else { for (col = 0; col <= 39; col++) { c = src[pos]; if (parodd(c)) { chr[pos] = 254; /* Parity error */ attr[pos] = 7; /* return 0 */ /*?*/ } else if ((c & 0x7f) >= 32) { /* Normal character */ c &= 0x7f; if (!graphics || (c >= 64 && c <= 95)) { chr[pos] = g0_to_iso_table [c]; attr[pos] = *lastattr; } else { chr[pos] = c + (c >= 96 ? 64 : 96); attr[pos] = *lastattr | VTX_G1; } } else { c &= 0x7f; chr[pos] = ((grhold && graphics ) ? *lastchr : ' '); if (c <= 7) { /* Set alphanumerics-color */ attr[pos] = *lastattr; next_attrib = (*lastattr & ~(VTX_COLMASK | VTX_HIDDEN)) + c; nextattr = 1; graphics = 0; } else if (c == 8 || c == 9) { /* Flash on/off */ attr[pos] = (*lastattr & ~VTX_FLASH) + VTX_FLASH * (c == 8); } else if (c == 10 || c == 11) { /* End/start box */ attr[pos] = (*lastattr & ~VTX_BOX) + VTX_BOX * (c == 11); } else if (c == 12 || c == 13) { /* Normal/double height */ attr[pos] = (*lastattr & ~VTX_DOUBLE1) + VTX_DOUBLE1 * (c == 13); if (c == 13) doubleht = 1; } else if (c == 14 || c == 15 || c == 27) { /* SO, SI, ESC (ignored) */ attr[pos] = *lastattr; } else if (c >= 16 && c <= 23) { /* Set graphics-color */ attr[pos] = *lastattr; next_attrib = (*lastattr & ~(VTX_COLMASK | VTX_HIDDEN)) + c - 16; nextattr = 1; graphics = 1; } else if (c == 24) { /* Conceal display */ attr[pos] = *lastattr | VTX_HIDDEN; } else if (c == 25 || c == 26) { /* Contiguous/separated graphics */ attr[pos] = (*lastattr & ~VTX_GRSEP) + VTX_GRSEP * (c == 26); } else if (c == 28) { /* Black background */ attr[pos] = *lastattr & ~VTX_BGMASK; } else if (c == 29) { /* Set background */ attr[pos] = (*lastattr & ~VTX_BGMASK) + ((*lastattr & VTX_COLMASK) << 3); } else if (c == 30 || c == 31) { /* Hold/release graphics */ attr[pos] = *lastattr; grhold = (c == 30); if (grhold && graphics) chr[pos] = *lastchr; } else { return 0; } } lastchr = chr + pos; if (nextattr) { lastattr = &next_attrib; nextattr = 0; } else { lastattr = attr + pos; } pos++; } } } return 1; } static SV * decode_ansi (u8 *chr, u16 *atr) { UI x; SV *sv = newSVpvn ("", 0); u16 o; for (x=0; x < VT_COLS; x++) { u16 a = *atr++; if (x == 0 || (a & 0x07) != (o & 0x07)) sv_catpvf (sv, "\x1b[3%dm", a & 7); if (x == 0 || (a & 0x38) != (o & 0x38)) sv_catpvf (sv, "\x1b[4%dm", (o & 0x38)>>3); if (x == 0 || (a & VTX_FLASH) != (o & VTX_FLASH)) sv_catpvf (sv, "\x1b[%sm", a & VTX_FLASH ? "7" : ""); sv_catpvf (sv, "%c", a & VTX_G1 ? 'x' : *chr); chr++; o = a; } sv_catpv (sv, "\x1b[37;40;0m"); return sv; } #define valid_packet(sv) (SvPOK(packet) && SvCUR(packet) == 40) #define consume_byte(store) \ if (bp >= 39) \ { \ SV **sv = av_fetch(stream, pi, 0); pi++; \ if (!sv) \ goto eostream; \ packet = *sv; \ if (!valid_packet (pascket)) \ goto skip; \ p = SvPV_nolen (packet); \ bp = 0; \ } \ (store) = p[++bp] static void decode_stream (AV *stream) { dSP; while (av_len (stream) >= 0) { UI pi = 1; SV *packet = *av_fetch(stream, 0, 1); if (valid_packet (packet)) { u8 *p = SvPV_nolen(packet); u8 bp = p[0] == 0xff ? p[1] : unham4 (p[0])*3+1; if (bp <= 12*3+1 && p[bp] == 0xa1) { u8 buf[4]; consume_byte (buf[0]); consume_byte (buf[1]); consume_byte (buf[2]); consume_byte (buf[3]); { u16 sh = unham8 (buf[0], buf[1]) | unham8 (buf[2], buf[3]) << 8; u8 bt = sh & 0x1f; u16 bl = sh >> 5; SV *block = sv_2mortal (newSVpvn (&bt, 1)); while (bl--) { consume_byte (buf[0]); sv_catpvn (block, buf, 1); } EXTEND (SP, 1); PUSHs (block); /* optimize, do only when still in first packet! */ do { if (bp >= 39) break; bp++; if (p[bp] == 0xa1) { p[0] = 0xff; p[1] = bp; pi--; break; } } while (p[bp] = 0x5e); } } } skip: while (pi--) SvREFCNT_dec(av_shift(stream)); } eostream: PUTBACK; } static u8 * unham_block (u8 *src, UI len, u8 *dst, UI dlen) { u16 sh = *src | (len-1)<<5; u8 sum = ( sh & 15) + ((sh >> 4) & 15) + ((sh >> 8) & 15) + ((sh >> 12) & 15); if (len < 5) return 0; sum += unham8 (src[1], src[2]); src += 3; len -= 3; dlen--; if (len < dlen) return 0; while (dlen) { *dst = unham8 (src[0], src[1]); sum += (*dst >> 4) + (*dst & 15); #if 0 printf ("%02x ", *dst); #endif dst++; src += 2; dlen--; } #if 0 printf ("\n"); printf ("sh = %04x, len = %02x, sum = %02x\n", sh, len, sum); #endif if (sum) return 0; return src; } /* must not be a macro, see nvec */ static U32 vec(u8 *d, UI bit, UI len) { U32 word = bit >> 3; /*printf ("vec(%d,%d) %d (%d)\n",bit,len,(bit & 7) + len,(bit & 7) + len < 32);*/ assert ((bit & 7) + len < 32); word = (d[word] ) | (d[word+1] << 8) | (d[word+2] << 16) | (d[word+3] << 24); return (word >> (bit & 7)) & (((U32)1 << len) - 1); } #define hv_store_sv(name,value) hv_store (hv, #name, strlen(#name), (SV*)(value), 0) #define hv_store_iv(name,value) hv_store_sv (name, newSViv (value)) #define nvec(bits) vec (c, (ofs += (bits)) - (bits), (bits)) #define decode_escape_sequences(av) \ { \ UI no_escapes = nvec (8); \ av = newAV (); \ while (no_escapes--) \ { \ AV *esc = newAV (); \ av_push (esc, newSViv (nvec (10))); \ av_push (esc, newSViv (nvec (6))); \ av_push (esc, newSViv (nvec (8))); \ av_push (av, newRV_noinc((SV*)esc)); \ } \ } #define decode_transparent_string(sv, len) \ { \ UI l = len; \ sv = newSVpvn (s, l); \ s += l; \ while (l--) \ SvPV_nolen (sv)[l] &= 0x7f; \ } #define decode_descriptor_loop(av, ll) \ av = newAV (); \ while (ll--) \ { \ AV *desc = newAV (); \ av_push (desc, newSViv (nvec (6))); \ av_push (desc, newSViv (nvec (6))); \ av_push (desc, newSViv (nvec (8))); \ av_push (av, newRV_noinc((SV*)desc)); \ } static void decode_epg (HV *hv, UI appid, u8 *c, u8 *s) { UI ofs = 16; UI len; AV *av; SV *sv; hv_store_iv (ca_mode, nvec(2)); hv_store_iv (_copyright, nvec(1)); ofs++; /* reserved */ switch (appid) { case 1: { hv_store_iv (epg_version, nvec( 6)); hv_store_iv (epg_version_swo, nvec( 6)); hv_store_iv (no_navigation, nvec(16)); hv_store_iv (no_osd, nvec(16)); hv_store_iv (no_message, nvec(16)); hv_store_iv (no_navigation_swo,nvec(16)); hv_store_iv (no_osd_swo, nvec(16)); hv_store_iv (no_message_swo, nvec(16)); len = nvec(8); hv_store_iv (this_network_op, nvec( 8)); decode_transparent_string (sv, nvec (5)); hv_store_sv (service_name, sv); hv_store_iv (no_updates, nvec( 1)); ofs += 2; av = newAV (); while (len--) { HV *hv = newHV (); int LTO; hv_store_iv (cni, nvec (16)); LTO = nvec (7) * 15; if (nvec(1)) LTO = -LTO; hv_store_iv (lto, LTO); hv_store_iv (no_days, nvec( 5)); decode_transparent_string (sv, nvec( 5)); hv_store_sv (netwop_name, sv); hv_store_iv (default_alphabet, nvec( 7)); hv_store_iv (prog_start_no, nvec(16)); hv_store_iv (prog_stop_no, nvec(16)); hv_store_iv (prog_stop_no_swo, nvec(16)); hv_store_iv (network_add_info, nvec(11)); av_push (av, newRV_noinc ((SV*)hv)); } hv_store_sv (networks, newRV_noinc ((SV*)av)); break; } case 2: { UI background_reuse; hv_store_iv (block_no, nvec(16)); hv_store_iv (audio_mode, vec(c, ofs, 12) & 3); hv_store_iv (feature_flags, nvec(12)); hv_store_iv (netwop_no, nvec( 8)); hv_store_iv (start_time, nvec(16)); hv_store_iv (start_date, nvec(16)); hv_store_iv (stop_time, nvec(16)); hv_store_iv (_pil, nvec(20)); hv_store_iv (parental_rating, nvec( 4)); hv_store_iv (editorial_rating,nvec( 3)); { UI no_themes = nvec( 3); UI no_sortcrit = nvec( 3); UI descriptor_looplength = nvec( 6); background_reuse = nvec( 1); av = newAV (); while (no_themes--) av_push (av, newSViv (nvec (8))); hv_store_sv (themes, newRV_noinc((SV*)av)); av = newAV (); while (no_sortcrit--) av_push (av, newSViv (nvec (8))); hv_store_sv (sortcrits, newRV_noinc((SV*)av)); decode_descriptor_loop (av, descriptor_looplength); hv_store_sv (descriptors, newRV_noinc((SV*)av)); ofs = (ofs+7) & ~7; decode_escape_sequences (av); hv_store_sv (title_escape_sequences, newRV_noinc((SV*)av)); decode_transparent_string (sv, nvec (8)); hv_store_sv (title, sv); } if (background_reuse) hv_store_iv (title_length, nvec (16)); else { decode_escape_sequences (av); hv_store_sv (shortinfo_escape_sequences, newRV_noinc((SV*)av)); decode_transparent_string (sv, nvec (8)); hv_store_sv (shortinfo, sv); len = nvec (3); ofs += 2; switch (len) { case 0: decode_escape_sequences (av); hv_store_sv (longinfo_escape_sequences, newRV_noinc((SV*)av)); decode_transparent_string (sv, nvec (8)); hv_store_sv (longinfo, sv); break; case 1: decode_escape_sequences (av); hv_store_sv (longinfo_escape_sequences, newRV_noinc((SV*)av)); decode_transparent_string (sv, nvec (10)); hv_store_sv (longinfo, sv); break; default: printf ("UNKNOWN LONGINFO TYPE %d\n", len); } } break; } case 3: { UI descriptor_ll; hv_store_iv (block_no, nvec(16)); hv_store_iv (header_size, nvec( 2)); len = nvec(4); hv_store_iv (message_size, nvec( 3)); ofs++; descriptor_ll = nvec(6); decode_descriptor_loop (av, descriptor_ll); hv_store_sv (descriptors, newRV_noinc((SV*)av)); ofs = (ofs+7) & ~7; decode_escape_sequences (av); hv_store_sv (header_escape_sequences, newRV_noinc((SV*)av)); decode_transparent_string (sv, nvec(8)); hv_store_sv (header, sv); hv_store_iv (message_attribute,nvec(8)); av = newAV (); while (len--) { AV *av2; UI len; HV *hv = newHV (); hv_store_iv (next_id, nvec(16)); hv_store_iv (next_type, nvec( 4)); len = nvec(4); av2 = newAV (); while (len--) { UI kind = nvec(8); av_push (av2, newSViv (kind)); switch (kind) { case 0x02: case 0x10: case 0x11: case 0x18: case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x40: case 0x41: av_push (av2, newSViv (nvec ( 8))); break; case 0x80: case 0x81: av_push (av2, newSViv (nvec (16))); break; case 0xc0: av_push (av2, newSViv (nvec (12))); av_push (av2, newSViv (nvec (12))); break; case 0xc8: case 0xc9: av_push (av2, newSViv (nvec (24))); break; default: abort (); } } hv_store_sv (attributes, newRV_noinc((SV*)av2)); decode_escape_sequences (av2); hv_store_sv (header_escape_sequences, newRV_noinc((SV*)av2)); decode_transparent_string (sv, nvec(8)); hv_store_sv (event, sv); av_push (av, newRV_noinc((SV*)hv)); } hv_store_sv (events, newRV_noinc((SV*)av)); break; } case 4: { UI block_no = nvec (16); UI descriptor_ll; UI msg_len; hv_store_iv (block_no, block_no); hv_store_iv (message_attribute,nvec(8)); hv_store_iv (header_size, nvec(3)); hv_store_iv (message_size, nvec(3)); descriptor_ll = nvec(6); if (!block_no) { decode_escape_sequences (av); hv_store_sv (message_escape_sequences, newRV_noinc((SV*)av)); msg_len = nvec(10); ofs += 6; } decode_escape_sequences (av); hv_store_sv (header_escape_sequences, newRV_noinc((SV*)av)); decode_transparent_string (sv, nvec(8)); hv_store_sv (header, sv); if (!block_no) { decode_transparent_string (sv, msg_len); hv_store_sv (message, sv); } decode_descriptor_loop (av, descriptor_ll); hv_store_sv (descriptors, newRV_noinc((SV*)av)); break; } case 5: { UI descriptor_ll; hv_store_iv (block_no, nvec(16)); descriptor_ll = nvec(6); decode_descriptor_loop (av, descriptor_ll); hv_store_sv (descriptors, newRV_noinc((SV*)av)); ofs = (ofs+7) & ~7; decode_escape_sequences (av); hv_store_sv (message_escape_sequences, newRV_noinc((SV*)av)); decode_transparent_string (sv, nvec(10)); hv_store_sv (message, sv); break; } default: printf ("UNKNOWN EPG DATATYPE ($%02x)\n", appid); } } static SV * decode_block(u8 *b, UI len, AV *bi) { dSP; u8 ctrl[1024]; u8 bt = *b; if (bt == 0) { if ((b = unham_block (b, len, ctrl, (len-1)>>1 ))) { UI app_no = ctrl[0]; UI app; av_clear (bi); for (app=1; app<=app_no; app++) av_store (bi, app, newSViv (vec (ctrl, app*16-8, 16))); } } else if (len >= 5) { if (bt <= av_len (bi) && SvOK(*av_fetch (bi, bt, 0))) { u16 appid = SvIV (*av_fetch (bi, bt, 0)); if (appid == 0) /* EPG */ { if ((b = unham_block (b, len, ctrl, unham8 (b[3], b[4]) | (unham4 (b[5]) << 8) & 1023))) { HV *hv = newHV (); /* _now_ we have an epg structure. decode it */ appid = vec(ctrl,10,6); EXTEND (SP, 2); PUSHs (sv_2mortal (newSViv (appid))); PUSHs (sv_2mortal (newRV_noinc ((SV*)hv))); decode_epg (hv, appid, ctrl, b); } else printf ("checksum error found block %d, len %d, appid = %d (clen 0)\n", bt, len, appid); } else /* other applications not defined (to my knowledge) */; } else /* no bundle info: can't parse yet */; } PUTBACK; } MODULE = Video::Capture::VBI PACKAGE = Video::Capture::VBI PROTOTYPES: ENABLE int unham4(data) SV * data CODE: STRLEN len; unsigned char *d = (unsigned char *)SvPV (data, len); if (len < 1) croak ("unham4: length must be at least 1"); RETVAL = unham4 (*d); OUTPUT: RETVAL int unham8(data) SV * data CODE: STRLEN len; unsigned char *d = (unsigned char *)SvPV (data, len); if (len < 2) croak ("unham8: length must be at least 2"); RETVAL = unham8 (d[0], d[1]); OUTPUT: RETVAL void decode_field(field, types) SV * field unsigned int types PPCODE: { UI lines = SvCUR(field) / VBI_BPL; UI line; decoder dec; decoder_init (&dec, types); EXTEND (SP, lines); for (line = 0; line < lines; line++) { SV *sv = decoder_decode (&dec, line, ((u8*)SvPV_nolen(field)) + line * VBI_BPL); if (sv) PUSHs (sv_2mortal (sv)); } } SV * decode_vps(char *data) SV * decode_vt(char *data) void decode_vtpage(data) SV * data PPCODE: { u8 chr[VT_COLS*VT_LINES]; u16 atr[VT_COLS*VT_LINES]; UI lines; if (!SvPOK(data)) XSRETURN_EMPTY; lines = SvCUR(data) / VT_COLS; if (lines > VT_LINES) croak ("videotext cannot have more than %d lines (argument has %d lines)\n", VT_LINES, lines); Zero(chr, VT_COLS*VT_LINES, u8); Zero(atr, VT_COLS*VT_LINES, u16); if (decode_vtpage (SvPV_nolen(data), lines, chr, atr)) { AV *av = newAV (); UI n; for (n = 0; n < VT_COLS*lines; n++) av_push (av, newSViv (atr[n])); EXTEND (SP, 2); PUSHs (sv_2mortal (newSVpvn (chr, VT_COLS*lines))); PUSHs (sv_2mortal (newRV_noinc ((SV*)av))); } } PROTOTYPES: DISABLE void decode_ansi(chr, atr) SV * chr SV * atr PPCODE: { UI lines = SvCUR(chr) / VT_COLS; UI attr_i = 0; u8 *_chr = SvPV_nolen (chr); u16 _atr[VT_COLS]; EXTEND (SP, lines); while (lines--) { UI attr_j; for(attr_j = 0; attr_j < VT_COLS; attr_j++) _atr[attr_j] = SvIV (*av_fetch ((AV*)SvRV (atr), attr_i+attr_j, 1)); PUSHs (sv_2mortal (decode_ansi (_chr, _atr))); _chr += VT_COLS; attr_i += VT_COLS; } } unsigned int bcd2dec(bcd) unsigned int bcd CODE: { UI digit = 1; RETVAL = 0; while (bcd) { if ((bcd & 15) > 9) XSRETURN_EMPTY; RETVAL += (bcd & 15) * digit; digit *= 10; bcd >>= 4; } } OUTPUT: RETVAL PROTOTYPES: ENABLE MODULE = Video::Capture::VBI PACKAGE = Video::Capture::VBI::EPG void decode_stream(stream) SV * stream PPCODE: if (!SvROK(stream) || SvTYPE(SvRV(stream)) != SVt_PVAV) croak ("decode_epg stream works on arrayrefs"); PUTBACK; decode_stream ((AV*)SvRV(stream)); SPAGAIN; SV * decode_block(block, bundle) SV * block SV * bundle PPCODE: if (!SvROK(bundle) || SvTYPE(SvRV(bundle)) != SVt_PVAV) croak ("bundle info must be arrayref"); PUTBACK; decode_block (SvPV_nolen(block), SvCUR(block), (AV*)SvRV(bundle)); SPAGAIN; BOOT: { HV *stash = gv_stashpvn("Video::Capture::VBI", 19, TRUE); newCONSTSUB(stash,"VBI_VT", newSViv(VBI_VT)); newCONSTSUB(stash,"VBI_VPS", newSViv(VBI_VPS)); newCONSTSUB(stash,"VBI_VDAT", newSViv(VBI_VDAT)); newCONSTSUB(stash,"VBI_VC", newSViv(VBI_VC)); newCONSTSUB(stash,"VBI_EMPTY", newSViv(VBI_EMPTY)); newCONSTSUB(stash,"VBI_OTHER", newSViv(VBI_OTHER)); newCONSTSUB(stash,"VTX_COLMASK",newSViv(VTX_COLMASK)); newCONSTSUB(stash,"VTX_GRSEP", newSViv(VTX_GRSEP)); newCONSTSUB(stash,"VTX_HIDDEN", newSViv(VTX_HIDDEN)); newCONSTSUB(stash,"VTX_BOX", newSViv(VTX_BOX)); newCONSTSUB(stash,"VTX_FLASH", newSViv(VTX_FLASH)); newCONSTSUB(stash,"VTX_DOUBLE1",newSViv(VTX_DOUBLE1)); newCONSTSUB(stash,"VTX_DOUBLE2",newSViv(VTX_DOUBLE2)); newCONSTSUB(stash,"VTX_INVERT", newSViv(VTX_INVERT)); newCONSTSUB(stash,"VTX_DOUBLE", newSViv(VTX_DOUBLE)); }