The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * parse various TV stuff out of DVB TS streams.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <iconv.h>

#include "parse-mpeg.h"

// dvb_lib
#include "dvb_debug.h"
#include "dvb_lib.h"

// Enable testing of multi-frequency handling
//#define TEST_MULTIFREQ

#ifdef TEST_MULTIFREQ

//idx = (tsid & 0xf000) >> 12
//
//[4107] 0x100B idx=1
//frequency = 578000000
//
//[8199] 0x2007 idx=2
//frequency = 850000000
//
//[12290] 0x3002 idx=3
//frequency = 713833330
//
//[16384] 0x4000 idx=4
//frequency = 721833330
//
//[20480] 0x5000 idx=5
//frequency = 690000000
//
//[24576] 0x6000 idx=6
//frequency = 538000000
//

// [idx][] - 1st entry is number of freqs, rest of entries are freq list
int other_freqs[8][8] = {
	/* 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, 
	/* 1 */ {3, 111111, 578000000, 12222222, 0, 0, 0, 0}, 
	/* 2 */ {4, 850000000, 21111111, 22222, 233333, 0, 0, 0}, 
	/* 3 */ {2, 311, 713833330, 0, 0, 0, 0, 0}, 
	/* 4 */ {3, 4111, 4222, 721833330, 0, 0, 0, 0}, 
	/* 5 */ {4, 51111, 52222, 53333, 690000000, 0, 0, 0}, 
	/* 6 */ {5, 611111, 622222, 633333, 644444, 538000000, 0, 0}, 
	/* 7 */ {0, 0, 0, 0, 0, 0, 0, 0}, 
} ;  

#endif


/* ----------------------------------------------------------------------- */
static unsigned int unbcd(unsigned int bcd)
{
    unsigned int factor = 1;
    unsigned int ret = 0;
    unsigned int digit;

    while (bcd) {
	digit   = bcd & 0x0f;
	ret    += digit * factor;
	bcd    /= 16;
	factor *= 10;
    }
    return ret;
}

/* ----------------------------------------------------------------------- */
static int iconv_string(char *from, char *to,
			char *src, size_t len,
			char *dst, size_t max)
{
    size_t ilen = (-1 != len) ? len : strlen(src);
    size_t olen = max-1;
    iconv_t ic;

    if (NULL == from)
	return 0;
    ic = iconv_open(to,from);
    if (NULL == ic)
	return 0;

    while (ilen > 0) {
	if (-1 == iconv(ic,&src,&ilen,&dst,&olen)) {
	    /* skip + quote broken byte unless we are out of space */
	    if (E2BIG == errno)
		break;
	    if (olen < 4)
		break;
	    sprintf(dst,"\\x%02x",(int)(unsigned char)src[0]);
	    src  += 1;
	    dst  += 4;
	    ilen -= 1;
	    olen -= 4;
	}
    }
    dst[0] = 0;
    iconv_close(ic);
    return max-1 - olen;
}

/* ----------------------------------------------------------------------- */
static int handle_control_8(unsigned char *src,  int slen,
			    unsigned char *dest, int dlen)
{
    int s,d;

    for (s = 0, d = 0; s < slen && d < dlen;) {
	if (src[s] >= 0x80  &&  src[s] <= 0x9f) {
	    switch (src[s]) {
	    case 0x86: /* <em>  */
	    case 0x87: /* </em> */
		s++;
		break;
	    case 0x1a: /* ^Z    */
		dest[d++] = ' ';
		s++;
		break;
	    case 0x8a: /* <br>  */
		dest[d++] = '\n';
		s++;
		break;
	    default:
		s++;
	    }
	} else {
	    dest[d++] = src[s++];
	}
    }
    return d;
}

/* ----------------------------------------------------------------------- */
void mpeg_parse_psi_string(char *src, int slen, char *dest, int dlen)
{
    char *tmp;
    int tlen ;
    unsigned ch = 0;
    unsigned first_byte = (unsigned)src[0] ;

//fprintf(stderr, "mpeg_parse_psi_string src len=%d [0x%02x ..], dest len=%d\n", slen, first_byte, dlen) ;

    if (first_byte < 0x20) {
		ch = first_byte;
		src++;
		slen--;
    }

    memset(dest,0,dlen);

//fprintf(stderr, " + ch = 0x%02x\n", ch) ;

    if (ch < 0x10) {
//fprintf(stderr, " + handle_control_8()\n") ;
		/* 8bit charset */
		tmp = malloc(slen);
//fprintf(stderr, " + + malloc() %p\n", tmp) ;
		tlen = handle_control_8(src, slen, tmp, slen);
//fprintf(stderr, " + + calling iconv_string() ...\n") ;
		iconv_string(psi_charset[ch], "UTF-8", tmp, tlen, dest, dlen);
//fprintf(stderr, " + + free() %p\n", tmp) ;
		free(tmp);
    } else {
//fprintf(stderr, " + iconv()\n") ;
		/* 16bit charset */
		iconv_string(psi_charset[ch], "UTF-8", src, slen, dest, dlen);
    }
//fprintf(stderr, "mpeg_parse_psi_string - DONE\n") ;
}

/* ======================================================================= */
/* DESCRIPTORS
 * 
 */

/* ----------------------------------------------------------------------- */
static void parse_nit_desc_1(unsigned char *desc, int dlen,
			     char *dest, int max)
{
    int i,t,l;

    if (dvb_debug>1)
		fprintf(stderr,
			"parse_nit_desc_1()\n");

    for (i = 0; i < dlen; i += desc[i+1] +2) {
	t = desc[i];
	l = desc[i+1];

    if (dvb_debug>1)
		fprintf_timestamp(stderr,
			"ts [nit1]: t 0x%02x   l %d\n",
			t, l);

	switch (t) {
	case 0x40:
	    mpeg_parse_psi_string((char*)desc+i+2, l, dest, max);
	    break;
	}
    }
}

/* ----------------------------------------------------------------------- */
static void parse_nit_desc_2(unsigned char *desc, int dlen,
			     struct psi_stream *stream, int tuned_freq)
{
    static char *bw[4] = {
	[ 0 ] = "8",
	[ 1 ] = "7",
	[ 2 ] = "6",
	[ 3 ] = "5",
    };
    static char *co_t[4] = { 
	[ 0 ] = "0",	/* QPSK */
	[ 1 ] = "16",
	[ 2 ] = "64",
    };
    static char *co_c[16] = {
	[ 0 ] = "0",
	[ 1 ] = "16",
	[ 2 ] = "32",
	[ 3 ] = "64",
	[ 4 ] = "128",
	[ 5 ] = "256",
    };
    static char *hi[4] = {
	[ 0 ] = "0",
	[ 1 ] = "1",
	[ 2 ] = "2",
	[ 3 ] = "4",
    };
    static char *ra_t[8] = {
	[ 0 ] = "12",
	[ 1 ] = "23",
	[ 2 ] = "34",
	[ 3 ] = "56",
	[ 4 ] = "78",
    };
    static char *ra_sc[8] = {
	[ 1 ] = "12",
	[ 2 ] = "23",
	[ 3 ] = "34",
	[ 4 ] = "56",
	[ 5 ] = "78",
    };
    static char *gu[4] = {
	[ 0 ] = "32",
	[ 1 ] = "16",
	[ 2 ] = "8",
	[ 3 ] = "4",
    };
    static char *tr[3] = {
	[ 0 ] = "2",
	[ 1 ] = "8",
	[ 2 ] = "4",
    };
    static char *po[4] = {
	[ 0 ] = "H",
	[ 1 ] = "V",
	[ 2 ] = "L",  // circular left
	[ 3 ] = "R",  // circular right
    };
    unsigned int freq,rate,fec;
    int i,j, t,l;

    if (dvb_debug>1)
		fprintf(stderr,
			"parse_nit_desc_2()\n");

    for (i = 0; i < dlen; i += desc[i+1] +2) 
    {
	t = desc[i];
	l = desc[i+1];

    if (dvb_debug>1)
		fprintf_timestamp(stderr,
			"ts [nit2]: t 0x%02x   l %d\n",
			t, l);


	switch (t) {
	case 0x43: /* dvb-s */
	    freq = mpeg_getbits(desc+i+2,  0, 32);
	    rate = mpeg_getbits(desc+i+2, 56, 28);
	    fec  = mpeg_getbits(desc+i+2, 85,  3);
	    stream->frequency     = unbcd(freq)    * 10;
	    stream->symbol_rate   = unbcd(rate*16) * 10;
	    stream->fec_inner     = ra_sc[fec];
	    stream->polarization  = po[   mpeg_getbits(desc+i+2, 49, 2) ];
	    break;
	    
	case 0x44: /* dvb-c */
	    freq = mpeg_getbits(desc+i+2,  0, 32);
	    rate = mpeg_getbits(desc+i+2, 56, 28);
	    fec  = mpeg_getbits(desc+i+2, 85,  3);
	    stream->frequency     = unbcd(freq)    * 100;
	    stream->symbol_rate   = unbcd(rate*16) * 10;
	    stream->fec_inner     = ra_sc[fec];
	    stream->constellation = co_c[ mpeg_getbits(desc+i+2, 52, 4) ];
	    break;
	    
	    
	case 0x5a: /* dvb-t */
		// terrestrial_delivery_system_descriptor

		//	centre_frequency 32 bslbf
		//	bandwidth 3 bslbf
		//	priority 1 bslbf
		//	Time_Slicing_indicator 1 bslbf
		//	MPE-FEC_indicator 1 bslbf
		//	reserved_future_use 2 bslbf
		//	constellation 2 bslbf
		//	hierarchy_information 3 bslbf
		//	code_rate-HP_stream 3 bslbf
		//	code_rate-LP_stream 3 bslbf
		//	guard_interval 2 bslbf
		//	transmission_mode 2 bslbf
		//	other_frequency_flag 1 bslbf
		//	reserved_future_use 32 bslbf

	    stream->frequency     = mpeg_getbits(desc+i+2,  0, 32) * 10;
	    stream->bandwidth     = bw[   mpeg_getbits(desc+i+2, 33, 2) ];
	    stream->constellation = co_t[ mpeg_getbits(desc+i+2, 40, 2) ];
	    stream->hierarchy     = hi[   mpeg_getbits(desc+i+2, 43, 2) ];
	    stream->code_rate_hp  = ra_t[ mpeg_getbits(desc+i+2, 45, 3) ];
	    stream->code_rate_lp  = ra_t[ mpeg_getbits(desc+i+2, 48, 3) ];
	    stream->guard         = gu[   mpeg_getbits(desc+i+2, 51, 2) ];
	    stream->transmission  = tr[   mpeg_getbits(desc+i+2, 54, 1) ];
	    stream->other_freq    = mpeg_getbits(desc+i+2, 55, 1);

if (dvb_debug>2)
	fprintf(stderr,
		"#@f terrestrial_delivery_system_descriptor: TSID %d freq=%d (other=%d) bw=%d (%s MHz) const=%d (%s) hier=%d (%s) rate hi=%d  (%s) rate lo=%d (%s) guard=%d (%s) tr=%d (%s) : up=%d tuned=%d\n",
		stream->tsid,
		stream->frequency,
		stream->other_freq,
		mpeg_getbits(desc+i+2, 33, 2),
		stream->bandwidth,
		mpeg_getbits(desc+i+2, 40, 2),
		stream->constellation,
		mpeg_getbits(desc+i+2, 43, 2),
		stream->hierarchy,
		mpeg_getbits(desc+i+2, 45, 3),
		stream->code_rate_hp,
		mpeg_getbits(desc+i+2, 48, 3),
		stream->code_rate_lp,
		mpeg_getbits(desc+i+2, 51, 2),
		stream->guard,
		mpeg_getbits(desc+i+2, 54, 1),
		stream->transmission,
		stream->updated, stream->tuned
		);
		
// Test where broadcast centre freq is invalid
#ifdef TEST_INVALID_CENTRE

		// mangle the real centre freq
		stream->frequency = stream->tsid ;

#endif


#ifdef TEST_MULTIFREQ

		// mangle the real centre freq
		stream->frequency = stream->tsid ;
		
	    // create freq list from table
		{
		int idx = (stream->tsid & 0xf000) >> 12 ;
		int num_freqs = other_freqs[idx][0] ;
    	unsigned int freq_index ;

if (dvb_debug>1)
	fprintf(stderr,
		"frequency_list_descriptor: num freqs=%d\n",
		num_freqs);
		
	    	
	    	stream->freq_list_len = num_freqs ;
	    	stream->freq_list = malloc(num_freqs * sizeof(int)) ;
	    	memset(stream->freq_list, 0, num_freqs * sizeof(int)) ;
		    for (freq_index=0; freq_index < num_freqs; ++freq_index) 
		    {
			int freq = other_freqs[idx][freq_index+1];
		
			    if (dvb_debug>1)
					fprintf(stderr,
						"frequency_list_descriptor: freq[%d]=%d\n",
						freq_index, freq);
	
				stream->freq_list[freq_index] = freq ;
		    }
	    
		}
#endif

if (dvb_debug > 1)
{
	fprintf(stderr,
		"terrestrial_delivery_system_descriptor:: Freq=%d\n",
		stream->frequency);
	
}
	    break;

	case 0x62: /* freq list */
		{
		unsigned int coding_type ;
		int num_freqs ;
	 
		//	frequency_list_descriptor(){
		//		descriptor_tag 8 uimsbf
		//		descriptor_length 8 uimsbf
		//		reserved_future_use 6 bslbf
		//		coding_type 2 bslbf
		//		for (i=0;I<N;i++){
		//			centre_frequency 32 uimsbf
		//		}
		//	}
		//	Table 54: Coding type values
		//	Coding_type Delivery system
		//	00 			not defined
		//	01 			satellite
		//	10 			cable
		//	11 			terrestrial

	    j = 0;
	    coding_type = mpeg_getbits(desc+i+2,  j+6, 2) ;
	    j+=8 ;
	    
	    num_freqs = (l - 1) / 4 ;

if (dvb_debug>1)
	fprintf(stderr,
		"frequency_list_descriptor: num freqs=%d\n",
		num_freqs);
		
	    if ((coding_type == 3) && (num_freqs > 0))
	    {
	    	unsigned int freq_index=0 ;
	    	
	    	stream->freq_list_len = num_freqs ;
	    	stream->freq_list = malloc(num_freqs * sizeof(int)) ;
	    	memset(stream->freq_list, 0, num_freqs * sizeof(int)) ;
		    while (j < l*8) 
		    {
			int freq ;
		
				freq = mpeg_getbits(desc+i+2,  j, 32) * 10 ;
		
			    if (dvb_debug>1)
					fprintf(stderr,
						"frequency_list_descriptor: freq[%d]=%d\n",
						freq_index, freq);
	
				stream->freq_list[freq_index++] = freq ;
		
				j += 32;
		    }
	    }
	    
		}
	    break;

#ifndef TEST_MULTIFREQ
	    
	case 0x83 : /* LCN */

	    j = 0;
	    while (j < l*8) 
	    {
		int sid, visible, lcn ;
	    struct prog_info  *pinfo;
		
	
			//	service_id 16 uimsbf
			//	visible_service_flag 1 bslbf
			//	reserved 5 bslbf
			//	logical_channel_number 10 uimsbf	
			sid			= mpeg_getbits(desc+i+2,  j, 16) ;
			visible		= mpeg_getbits(desc+i+2,  j+17, 1) ;
			lcn 		= mpeg_getbits(desc+i+2,  j+22, 10) ;
	
		    if (dvb_debug>1)
				fprintf(stderr,
					"#@p LCN: service_id=%d (0x%04x)  visible=%d  lcn=%d (0x%03x)\n",
					sid, sid, visible, lcn, lcn);
					
			pinfo = prog_info_get(stream, sid, 1) ;
			pinfo->visible = visible ;
			pinfo->lcn = lcn ;
	
			j += 32;
	    }

		break ;
#endif

		
	case 0x41:  /* service list descriptor */
	
		//service_list_descriptor(){
		//	descriptor_tag 8 uimsbf
		//	descriptor_length 8 uimsbf
		//	for (i=0;i<N;I++){
		//		service_id 16 uimsbf
		//		service_type 8 uimsbf
		//	}
		//}
	
	    j = 0;
	    while (j < l*8) 
	    {
		int sid, service_type ;
	    struct prog_info  *pinfo;
	
			sid				= mpeg_getbits(desc+i+2,  j, 16) ;
			service_type	= mpeg_getbits(desc+i+2,  j+17, 8) ;
	
		    if (dvb_debug>1)
				fprintf(stderr,
					"service_list_descriptor: service_id=%d (0x%04x)  service_type=%d (0x%02x)\n",
					sid, sid, service_type, service_type);

			pinfo = prog_info_get(stream, sid, 1) ;
			pinfo->service_type = service_type ;
	
			j += 24;
	    }
		break ;	
		
	}
    }
    return;
}

/* ----------------------------------------------------------------------- */
static void parse_sdt_desc(unsigned char *desc, int dlen,
			   struct psi_program *pr, int tuned_freq, int verbose)
{
    int i,t,l;
    char *name,*net;

    for (i = 0; i < dlen; i += desc[i+1] +2) {
	t = desc[i];
	l = desc[i+1];

	switch (t) {
	case 0x48:
	    pr->type = desc[i+2];
	    pr->updated = 1;
	    net = (char*)desc + i+3;
	    name = net + net[0] + 1;
	    mpeg_parse_psi_string(net+1,  net[0],  pr->net,  sizeof(pr->net));
	    mpeg_parse_psi_string(name+1, name[0], pr->name, sizeof(pr->name));

		if (verbose) fprintf(stderr,"    pnr %5d  %s\n", pr->pnr, pr->name);

	    if (dvb_debug > 2)
	    	fprintf(stderr,"#@p parse_sdt_desc() : tuned=%d : tsid=%d pnr=%d name=%s [v=%d a=%d]\n",
	    	tuned_freq,
	    	pr->tsid, pr->pnr, pr->name, pr->v_pid, pr->a_pid);
	    break;
	}
    }
}

/* ======================================================================= */
/* TABLES
 * 
 */

/* ----------------------------------------------------------------------- */
int mpeg_parse_psi_sdt(struct psi_info *info, unsigned char *data, int verbose, int tuned_freq)
{
    static const char *running[] = {
	[ 0       ] = "undefined",
	[ 1       ] = "not running",
	[ 2       ] = "starts soon",
	[ 3       ] = "pausing",
	[ 4       ] = "running",
	[ 5 ... 8 ] = "reserved",
    };
    struct psi_program *pr;
    int tsid,pnr,version,current;
    int j,len,dlen,run,ca;

    len     = mpeg_getbits(data,12,12) + 3 - 4;
    tsid    = mpeg_getbits(data,24,16);
    version = mpeg_getbits(data,42,5);
    current = mpeg_getbits(data,47,1);
    if (!current)
    	return len+4;
    if (info->tsid == tsid && info->sdt_version == version)
    	return len+4;
    info->sdt_version = version;

    if (verbose>1)
		fprintf_timestamp(stderr,
			"ts [sdt]: tsid %d ver %2d [%d/%d]\n",
			tsid, version,
			mpeg_getbits(data,48, 8),
			mpeg_getbits(data,56, 8));

    j = 88;
    while (j < len*8) {
		pnr  = mpeg_getbits(data,j,16);
		run  = mpeg_getbits(data,j+24,3);
		ca   = mpeg_getbits(data,j+27,1);
		dlen = mpeg_getbits(data,j+28,12);
		if (verbose > 2) {
			fprintf(stderr,"   (freq=%d) pnr %3d ca %d %s #",
				tuned_freq, pnr, ca, running[run]);
			mpeg_dump_desc(data+j/8+5,dlen);
			fprintf(stderr,"\n");
		}
		pr = psi_program_get(info, tsid, pnr, tuned_freq, 1);
		parse_sdt_desc(data+j/8+5,dlen,pr,tuned_freq, verbose);
		pr->running = run;
		pr->ca      = ca;
		j += 40 + dlen*8;
    }
    if (verbose > 2)
    	fprintf(stderr,"\n");
    return len+4;
}

/* ----------------------------------------------------------------------- */
//	network_information_section(){
//		table_id	8	uimsbf
//		section_syntax_indicator	1	bslbf
//		reserved_future_use	1	bslbf
//		reserved	2	bslbf
//		section_length	12	uimsbf
//		
//		network_id	16	uimsbf
//		reserved	2	bslbf
//		version_number	5	uimsbf
//		current_next_indicator	1	bslbf
//		section_number	8	uimsbf
//		last_section_number	8	uimsbf
//		
//		reserved_future_use	4	bslbf
//		network_descriptors_length	12	uimsbf
//		for(i=0;i<N;i++){
//			descriptor()
//		}
//		reserved_future_use	4	bslbf
//		transport_stream_loop_length	12	uimsbf
//		for(i=0;i<N;i++){
//			transport_stream_id	16	uimsbf
//			original_network_id	16	uimsbf
//			reserved_future_use	4	bslbf
//			transport_descriptors_length	12	uimsbf
//			for(j=0;j<N;j++){
//				descriptor()
//			}
//		}
//		CRC_32	32	rpchof
//	}
int mpeg_parse_psi_nit(struct psi_info *info, unsigned char *data, int verbose, int tuned_freq)
{
    struct psi_stream *stream;
    char network[PSI_STR_MAX] = "";
    int id,version,current,len;
    int j,dlen,tsid;


    len     = mpeg_getbits(data,12,12) + 3 - 4;
    id      = mpeg_getbits(data,24,16);		/* network_id */
    version = mpeg_getbits(data,42,5);
    current = mpeg_getbits(data,47,1);

    if (!current)
    	return len+4;

    if (0 /* info->id == id */ && info->nit_version == version)
    	return len+4;
    info->nit_version = version;

    j = 80;
    dlen = mpeg_getbits(data,68,12);
    parse_nit_desc_1(data + j/8, dlen, network, sizeof(network));

    if (verbose>1) {
		fprintf_timestamp(stderr,
			"ts [nit]: id %3d ver %2d [%d/%d] #",
			id, version,
			mpeg_getbits(data,48, 8),
			mpeg_getbits(data,56, 8));
		mpeg_dump_desc(data + j/8, dlen);
		fprintf(stderr,"\n");
    }
    j += 16 + 8*dlen;

    while (j < len*8) {
    	tsid = mpeg_getbits(data,j,16);
        dlen = mpeg_getbits(data,j+36,12);
		j += 48;
		
		stream = psi_stream_get(info, tsid, id, 1);	
		
		stream->updated = 1;
		if (network)
			strcpy(stream->net, network);
		parse_nit_desc_2(data + j/8, dlen, stream, tuned_freq);
		if (verbose > 2) {
			fprintf(stderr,"   tsid %3d #", tsid);
			mpeg_dump_desc(data + j/8, dlen);
			fprintf(stderr,"\n");
		}
		j += 8*dlen;
    }

    if (verbose > 2)
    	fprintf(stderr,"\n");

    return len+4;
}