/************************************************************************* * Copyright 2009/2010/2011 Ralph Spitzner (rasp@spitzner.org) * * This file is part of v2Yahdr. * * v2Yahdr 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. * * Yahdr 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 v2Yahdr. If not, see . **************************************************************************/ /** use adapter !busy && !watching || tuned **/ ///#error timeout bei dem einen transponder evtl. crc fehler ? //#error look at dvbsnoop's read routine for timeout and open flags, maybe have time_read without TMP_FAILURE, but time via time(0) #include "ydefs.h" //static uint8_t sections[255]; #define EITPID 0x12 /** table 50 should give ~4 days info on table 0x51 very sparse never seen any table above 0x51 change mask and the s_index line in do_data() if you want more... **/ #define TABLE 0x50 #define MASK 0xff //static sqlite3 *epg_db; static sqlite3_stmt *epg_stmt; static int epg_rc; static uint8_t checksec(uint8_t *sections,uint8_t have,uint8_t want) { uint8_t i,full; full = true; if(sections[have] == 0) { sections[have] = 1; return S_WANT; } for(i=0; i != want; i++) { if(sections[i] == 0) { full = false; break; } } if(full == false) return S_HAVE; ///else return S_DONE; } static SCONTROL * get_services(uint16_t tsid) { SCONTROL *ctrl,*cp; char pbuf[255]; ctrl = cp = NULL; /** can't use eit flag on my DVB-T or cable because of stupid multiplex operators..... **/ sprintf(pbuf,"select s_id,e_vers from prg where tsid=%d and eit=1",tsid); rc = sqlite3_prepare_v2(db, pbuf, -1, &stmt, 0); if( rc ) { fprintf(stderr, "SQL error: %d : %s @ %s %d\n", rc, sqlite3_errmsg(db),__FILE__,__LINE__); exit(0); } else { cols = sqlite3_column_count(stmt); do { rc = sqlite3_step(stmt); switch( rc ) { case SQLITE_DONE: break; case SQLITE_ROW: if(ctrl == NULL) { if(!(ctrl = malloc(sizeof(SCONTROL)))) { printf("mem @ %d %s",__LINE__,__FILE__); perror(""); exit(0); } bzero(ctrl,sizeof(SCONTROL)); cp = ctrl; } else if(!(cp->next = malloc(sizeof(SCONTROL)))) { printf("mem @ %d %s",__LINE__,__FILE__); perror(""); exit(0); } else { bzero(cp->next,sizeof(SCONTROL)); cp = cp->next; } cp->s_id = sqlite3_column_int(stmt,0); cp->version = sqlite3_column_int(stmt,1); break; default: fprintf(stderr, "Error: %d : %s\n", rc, sqlite3_errmsg(db)); break; } } while( rc==SQLITE_ROW ); sqlite3_finalize(stmt); } return ctrl; } static void stuff_events(EVENT *ev) { /// database opened in epg_runner() char sql[8192]; bool had; EVENT *first_ev; uint16_t ids[64*1024]; uint16_t idi,idmax; time_t dummy; bool update; first_ev = ev; idi = idmax = 0; if(ev->start_time == 0) { return; } sprintf(sql,"select event_id from epg where tsid='%d' and s_id='%d'", ev->tsid,ev->s_id); epg_rc = sqlite3_prepare_v2(epg_db, sql, -1, &epg_stmt, 0); had = false; if( epg_rc ) { fprintf(stderr, "SQL error: %d : %s @ %s %d\n", epg_rc, sqlite3_errmsg(epg_db),__FILE__,__LINE__); exit(0); } else { do { epg_rc = sqlite3_step(epg_stmt); switch( epg_rc ) { case SQLITE_DONE: break; case SQLITE_ROW: ids[idi] = sqlite3_column_int(epg_stmt, 0); idi++; break; default: fprintf(stderr, "Error: %d : %s\n", epg_rc, sqlite3_errmsg(epg_db)); break; } } while( epg_rc==SQLITE_ROW ); sqlite3_finalize(epg_stmt); } sprintf(sql,"begin transaction"); epg_rc = sqlite3_prepare_v2(epg_db, sql, -1, &epg_stmt, 0); if( epg_rc ) { fprintf(stderr, "SQL error: %d : %s @ %s %d\n", epg_rc, sqlite3_errmsg(epg_db),__FILE__,__LINE__); fprintf(stderr,"SQL was :>%s<\n",sql); return ; } epg_rc = sqlite3_step(epg_stmt); sqlite3_finalize(epg_stmt); idmax = idi; while(ev->start_time) { update = false; for(idi = 0; idi != idmax; idi++) { if((ev->event_id == ids[idi])) { update = true; break; } } if(update == true) { /*** update this event, this also means old data might get lost. archive this stuff ? ***/ dummy = ev->start_time; //printf("update >%s< id:%d @ %s",ev->name,ev->event_id, ctime(&dummy)); sprintf(sql,"update epg set start_time='%lld',duration='%d',name='%s',short_des='%s',long_des='%s' where tsid='%d' and s_id='%d' and event_id='%d'", ev->start_time, ev->duration, &ev->name[0], &ev->short_d[0], &ev->long_d[0], ev->tsid, ev->s_id, ev->event_id ); } else { dummy = ev->start_time; //printf("insert >%s< id:%d @ %s",ev->name,ev->event_id, ctime(&dummy)); sprintf(sql,"insert into epg values('%d','%d','%d','%lld','%d','%s','%s','%s')", ev->tsid, ev->s_id, ev->event_id, ev->start_time, ev->duration, &ev->name[0], &ev->short_d[0], &ev->long_d[0] ); } epg_rc = sqlite3_prepare_v2(epg_db, sql, -1, &epg_stmt, 0); if( epg_rc ) { fprintf(stderr, "SQL error: %d : %s @ %s %d\n", epg_rc, sqlite3_errmsg(epg_db),__FILE__,__LINE__); fprintf(stderr,"SQL was :>%s<\n",sql); return ; } epg_rc = sqlite3_step(epg_stmt); sqlite3_finalize(epg_stmt); ev = ev->next; } sprintf(sql,"commit"); INFO("commit\n"); epg_rc = sqlite3_prepare_v2(epg_db, sql, -1, &epg_stmt, 0); if( epg_rc ) { fprintf(stderr, "SQL error: %d : %s @ %s %d\n", epg_rc, sqlite3_errmsg(epg_db),__FILE__,__LINE__); fprintf(stderr,"SQL was :>%s<\n",sql); return ; } epg_rc = sqlite3_step(epg_stmt); sqlite3_finalize(epg_stmt); } static void translit(uint8_t *p) { switch(*p) { case 0x86 ... 0x87: *p = '*'; /// emphasis on/off break; case 0x8a: *p = '\n'; break; case 0xa0: /// they call it 'nbsp', most likely 'non breaking space' *p = ' '; break; } } static EVENT * read_descriptors(uint16_t tsid,uint16_t s_id,uint8_t *data,uint16_t o_dlen,EVENT *start_event) { /** care about: 0x4e extended event 0x4d short event these are not really used (right) 0x53 ca desc. 0x5e multiling 0x50 component content/PDC is a load of *BS* most of the time **/ EVENT *ev; uint8_t *p; int dlooplen; int ti,di,dlen; ev = NULL; p = data; dlen = o_dlen; ev = start_event; if(ev == NULL) { printf("what'cha calling me, for nuthin ?\n"); return NULL; } while(dlen > 0) { ev->tsid = tsid; ev->s_id = s_id; ev->event_id = (*p << 8) & 0xff00; p++; dlen--; ev->event_id |= *p; // printf("############ event_id = %d \n",ev->event_id); p++; dlen--; ev->start_time = convert_date((char *)p); p = p + 5; /// advance 40 bits dlen -= 5; ev->duration = bcdtoint(*p) * 60 * 60; p++; dlen--; ev->duration += bcdtoint(*p) * 60; p++; dlen--; ev->duration += bcdtoint(*p); p++; dlen--; dlooplen = (*p & 0x0f) << 8; // upper 4 bits are running status/ca mode p++; dlen--; dlooplen |= (*p & 0xff); p++; dlen--; dlen -= dlooplen; ///// descriptors start here while(dlooplen > 0) { switch(*p) { case 0x4d: ///short event p++; dlooplen--; dlooplen -= *p; p++; p += 3; /// langcode di = *p & 0xff; ti = 0; p++; if(di != 0) { if(*p < 0x20) { di--; p++; /// charset } while(di) { translit(p); if(*p == '\'') ev->name[ti++] = '\''; ev->name[ti++] = *p++; di--; } } di = *p & 0xff; ti = 0; p++; if(di != 0) { if(*p < 0x20) { di--; p++; } while(di) { translit(p); if(*p == '\'') ev->short_d[ti++] = '\''; ev->short_d[ti++] = *p++; di--; } } //else //p++; dlooplen--; break; case 0x4e: /// extended event p++; dlooplen--; dlooplen -= *p; ///sub len of this thing p++; p++; ///desc #, last desc (never seen theese out of sync, disregard) p += 3; /// forget about language code if(*p != '\0') { printf("nanu?\n"); getchar(); } p += *p; /// forward lentgh of items (never seen anything but 0 in here) p++; di = *p & 0xff; /// text len p++; ti = strlen(ev->long_d); /// index in array if(di) { if(*p < 0x20) { di--; p++; /// junk charset } while(di != 0) { translit(p); if(*p == '\'') ev->long_d[ti++] = '\''; ev->long_d[ti++] = *p++; di--; } } /// no need to pad, array bzero'ed dlooplen--; break; default: /// something we don't care about, just advance //printf("descriptor 0x%x dlen %d, dlooplen %d sub %d\n",*p,dlen,dlooplen,*(p+1) + 2); p++; dlooplen--; dlooplen -= *p; p = p + *p; p++; dlooplen--; break; } } if(!(ev->next = malloc(sizeof(EVENT)))) { perror("alloc next event"); exit(0); } else { bzero(ev->next,sizeof(EVENT)); ev = ev->next; } } return ev; /* while(ev) { printf("###### ID %d\n",ev->event_id); printf("start: %s",ctime((time_t *)&ev->start_time)); printf("durat: %d min\n",ev->duration / 60); printf("name : >%s<\n",ev->name); printf("short: >%s<\n",ev->short_d); printf("descr: >%s<\n",ev->long_d); ev = ev->next; }*/ /// free events ! } void printsec(SCONTROL *s) { int i; printf("=====================================================================================\n"); while(s) { printf("50 "); for(i=0; i!=255; i++) { if(s->sections[0][i] == 0) printf("-"); else if(i % 8) printf("*"); else printf("|"); } printf("\n"); printf("51 "); for(i=0; i!=255; i++) { if(s->sections[1][i] == 0) printf("-"); else if(i % 8) printf("*"); else printf("|"); } printf("\n"); s=s->next; } } static void update_services(SCONTROL *s,uint16_t tsid) { char sql[255]; while(s) { sprintf(sql,"update prg set e_vers='%d' where tsid='%d' and s_id='%d'", s->version, tsid, s->s_id); rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); if( rc ) { fprintf(stderr, "SQL error: %d : %s @ %s %d\n", rc, sqlite3_errmsg(db),__FILE__,__LINE__); exit(0); } rc = sqlite3_step(stmt); sqlite3_finalize(stmt); s= s->next; } } /** Sadly enough this want's a global maximum timeout, for dorky cable providers/operators who don't set their EIT/PF flags right won't barr us forever.... **/ static void do_data(int fd,uint16_t tsid) { uint8_t *bp; uint8_t seglast,seclast,secno,last_table_id,tmp_secno,checkvar; int plen,s_index; SCONTROL *scont,*sp,*p; uint8_t pbuf[4096]; uint16_t seclen,s_id; bool happy; time_t start_time,now; uint16_t actual_ts; EVENT *first_event,*evp,*evtmp; char version; int x; bp = pbuf; scont = get_services(tsid); if(scont == NULL) return; /// no service with EIT here first_event = evp = evtmp = NULL; if(!(first_event = malloc(sizeof(EVENT)))) { perror("malloc first event"); exit(0); } bzero(first_event,sizeof(EVENT)); evp = first_event; start_time = time(0); while(1) { //printsec(scont); now = time(0); if(now - start_time > EPGMAXWAIT) { printf("\ntimeout of %d sec. reached, abort\n",EPGMAXWAIT); break; } /// check if bucket is full sp = scont; bzero(pbuf,4096); while(sp) { if(sp->done == 0) { happy = false; break; } else { happy = true; } sp = sp->next; } if(happy == true) /// all done { printf("done\n"); break; } bp = &pbuf[0]; if((plen = time_read(fd,10,bp,4096)) <= 0) { perror("do_data "); return; } //printf("data for table 0x%x ",*bp); s_index = *bp & 0xf; bp++; seclen = (*bp << 8) & 0xf00; bp++; seclen |= *bp & 0xff; seclen -= 4; /// crc done by dvb api //printf("plen %d seclen %d ",plen,seclen); bp++; seclen--; s_id = *bp << 8; bp++; seclen--; s_id |= *bp; bp++; seclen--; version = ((*bp & 0xff) >> 1) & 0x1f; /// version number if((*bp & 1) == 0) /// not valid now printf(" NOT VALID \n"); bp++; seclen--; secno = *bp; bp++; seclen--; seclast = *bp; bp++; seclen--; /// upper 8 tsid actual_ts = (*bp & 0xff) << 8; bp++; seclen--; /// lower 8 tsid actual_ts |= *bp & 0xff; if(actual_ts != tsid) { /// someone tuned us away printf("actual %d, ts %d\n",actual_ts,tsid); return; } bp++; seclen--; /// upper 8 network id bp++; seclen--; /// lower 8 network id bp++; seclen--; seglast = *bp; /// last section in segment //printf("s_id=%d secno %d seglast %d lastsec %d\n",s_id,secno,seglast,seclast); bp++; seclen--; last_table_id = *bp; /// check/set section sp = scont; //if(s_id != 15) // continue; while(sp) { if(sp->s_id == s_id) break; sp = sp->next; } if(sp == NULL) continue; /// something for some service we don't know/want if(sp->done == true) continue; while(seglast % 8 != 0) { sp->sections[s_index][seglast] = 1; seglast++; } if((secno == seglast) && (secno % 8 == 0)) { tmp_secno = secno+1; while(tmp_secno % 8 != 0) { sp->sections[s_index][tmp_secno] = 1; tmp_secno++; } } checkvar = checksec(&sp->sections[s_index][0],secno,seclast); switch(checkvar) { case S_HAVE: continue; /// already seen that section case S_DONE: sp->done = true; sp->version = version; continue; default: break; } /* this does not work, since multiplexes do not set their version correct if(sp->version == version) /// already have that in db { printf("Version not changed\n"); for(x=0; x!=255; x++) { sp->sections[s_index][x] = 1; /// mark sections of service 'received' } continue; }*/ /// should be safe to look for descriptors now :-P if(seclen == 0) continue; /// empty bp++; seclen--; /// else events start here //printf("."); //fflush(stdout); evp = read_descriptors(tsid,s_id,bp,seclen,evp); ///reset watchdog if data was useful start_time = time(0); } printf("\nstoring....\n"); stuff_events(first_event); printf("done\n"); sp = scont; update_services(sp,tsid); /// free events evp = evtmp = first_event; while(evp) { evtmp = evp->next; free(evp); evp = evtmp; } sp = scont; while(sp) { p = sp->next; free(sp); sp = p; } } void * epg_runner(void *nix) { char pbuf[255]; int demux_fd; ADAPTER *adp; struct dvb_frontend_parameters fe_parm; struct dmx_sct_filter_params sctFilterParams; TRANSPORT *ts,*tsp,*p; time_t now; /* epg_rc = sqlite3_open(EPG_DB_NAME,&epg_db); if(epg_rc) { fprintf(stderr, "Can't open database: %s @ %d %s\n", sqlite3_errmsg(db),__LINE__,__FILE__); return false; }*/ tsp = NULL; while(1) { tsp = transports; ///rewind /* while(tsp) { if(tsp->tsid == 10001) break; tsp = tsp->next; }*/ while(tsp) { adp = adapters; while(adp) { if(adp->number == tsp->adapter) break; adp=adp->next; } if(adp == NULL) { printf("ouch, adapter not found %d %s\n",__LINE__,__FILE__); exit(0); } /** See if someone's watching, or adapter set for recording **/ if(adp->watching == true || adp->busy == true || !(adp->pref & WANT_EPG)) { tsp = tsp->next; while(adp->watching == true) { now = time(0); fprintf(stderr,"not reading epg, adapter in use (watching==1) @ %s",ctime(&now)); sleep(1800); //test every half hour } //continue; } now = time(NULL); printf("reading EPG for %d on %d tsid %d at %s\n",tsp->freq,adp->number,tsp->tsid,ctime(&now)); bzero(&fe_parm,sizeof(struct dvb_frontend_parameters)); bzero(&sctFilterParams,sizeof(struct dmx_sct_filter_params)); switch(adp->type) { case TERR: fe_parm.inversion = INVERSION_AUTO; fe_parm.u.ofdm.code_rate_HP =FEC_3_4; fe_parm.u.ofdm.code_rate_LP = FEC_AUTO; fe_parm.u.ofdm.constellation = QAM_AUTO; //16; // ?? fe_parm.u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO;//8K; fe_parm.u.ofdm.guard_interval = GUARD_INTERVAL_AUTO;//1_8; fe_parm.u.ofdm.bandwidth = tsp->bw == 7 ? BANDWIDTH_7_MHZ : BANDWIDTH_8_MHZ ; break; case CABLE: fe_parm.inversion = INVERSION_AUTO; fe_parm.u.qam.fec_inner = FEC_AUTO; fe_parm.u.qam.symbol_rate = 6900000; fe_parm.u.qam.modulation = tsp->qam; break; default: printf("Unknown adapter type %d\n",adp->type); return false; } fe_parm.frequency = tsp->freq; if(adp->fe_fd == 0) { sprintf(pbuf,"/dev/dvb/adapter%d/frontend0",adp->number); if((adp->fe_fd = open(pbuf,O_RDWR)) < 0 ) { perror("epg frontend open"); exit(0); } } if (ioctl(adp->fe_fd, FE_SET_FRONTEND, &fe_parm) < 0) { perror("ioctl FE_SET_FRONTEND failed"); return false; } check_frontend(adp->fe_fd,8); sprintf(pbuf,"/dev/dvb/adapter%d/demux0",adp->demux); if((demux_fd = open(pbuf,O_RDWR)) < 0) { perror("epg demux open"); exit(0); } sctFilterParams.pid = EITPID; sctFilterParams.timeout = 0; sctFilterParams.flags = DMX_IMMEDIATE_START | DMX_CHECK_CRC; sctFilterParams.filter.filter[0] = TABLE; sctFilterParams.filter.mask[0] = MASK; if( ioctl( demux_fd, DMX_SET_BUFFER_SIZE, 128*4096) < 0) { perror("DMX buffer"); exit(0); } if (ioctl(demux_fd, DMX_SET_FILTER, &sctFilterParams) < 0) { printf("DMX filter set failed in read_tpinfo ! (fd = %d) %s,%d\n",demux_fd,__FILE__,__LINE__); perror(""); exit(0); } /* if(input_timeout(demux_fd,30) <= 0) { perror("read from demux"); close(demux_fd); continue; }*/ do_data(demux_fd,tsp->tsid); close(demux_fd); tsp = tsp->next; sleep(10); }/// end wile tsp /** Just sleep a while **/ sleep(EPGSLEEP); //exit(0); //debug }/// end while 1 tsp = ts; while(tsp) { p = tsp->next; free(tsp); tsp = p; } return NULL; }