#include #include #include #include "getstream.h" #include "psi.h" struct programcb_s { void (*callback)(void *data, void *arg); void *arg; }; struct pmtes_s { uint8_t streamtype; uint16_t espid; }; struct pmt_s { uint16_t pnr; uint16_t pcrpid; GList *es; }; struct program_s { struct adapter_s *adapter; unsigned int pnr; uint16_t pmtpid; void *pmtpidcb; /* pid callback cookie (PMT) */ struct { GList *cb; } pidtable[PID_MAX]; struct psi_s psi; struct pmt_s *pmtlast; struct pmt_s *pmtcurrent; GList *progcbl; }; #if 0 /* Callback for program pids (not pmt) from the demux */ static void pmt_dvr_cb(void *data, void *arg) { struct program_s *prog=arg; GList *progcbl=g_list_first(prog->progcbl); while(progcbl) { struct programcb_s *pcb=progcbl->data; /* Issue callback - typically a input callback */ pcb->callback(data, pcb->arg); progcbl=g_list_next(progcbl); } } #endif #define pmt_prog_gets_pid(prog, pid) \ ((prog)->pidtable[(pid)].cb != NULL) /* * This program wants a specific PID - So register it with the DVR demux. As we * might habe multiple streams receiving this program we register all streams * with the dvr demux. * * As we need to leave pid in case the PMT changes we need to remember the DVR demux * call back cookies. For this we append the cookies to a GList in the programs pidtable. * */ static void pmt_prog_join_pid(struct program_s *prog, uint16_t pid, unsigned int type) { GList *pcbl=g_list_first(prog->progcbl); while(pcbl) { struct programcb_s *pcb=pcbl->data; void *cbc; cbc=dvr_add_pcb(prog->adapter, pid, DVRCB_TS, type, pcb->callback, pcb->arg); prog->pidtable[pid].cb=g_list_append(prog->pidtable[pid].cb, cbc); pcbl=g_list_next(pcbl); } } /* * The programs leaves a certain pid. For this we walk the DVR callback cookie * list in the programs pidtable and pass them onto the dvr_del_pcb. * If we removed all streams from the dvr callback we delete the list and * set the GList pointer in the pidtable to NULL which is the signal that * the program is not receiving that pid. */ static void pmt_prog_leave_pid(struct program_s *prog, uint16_t pid) { GList *cbcl=g_list_first(prog->pidtable[pid].cb); while(cbcl) { void *cbc=cbcl->data; dvr_del_pcb(prog->adapter, pid, cbc); cbcl=g_list_next(cbcl); } g_list_free(prog->pidtable[pid].cb); prog->pidtable[pid].cb=NULL; } static uint16_t pmt_pnr(struct psisec_s *section) { return (section->data[PMT_PNR_OFF1] << 8 | section->data[PMT_PNR_OFF2]); } static int pmt_mapstreamtype(uint8_t type) { switch (type) { case 1: /* ISO/IEC 11172 Video */ case 2: /* ITU-T Rec. H.262 */ /* ISO/IEC 13818-2 Video */ /* ISO/IEC 11172-2 */ case 27: /* ITU-T Rec. H.264 */ /* ISO/IEC 14496-10 Video */ return PID_VIDEO; break; case 3: /* ISO/IEC 11172 Audio */ case 4: /* ISO/IEC 13818-3 Audio */ return PID_AUDIO; break; case 5: /* ISO/IEC 13818-1 Page 160 - Private Sections */ case 6: /* ITU-T Rec. H.222.0 ISO/IEC 13818-1 PES - Private Data */ /* Stephen Gardner sent dvbsnoop output showing AC3 Audio * in here encapsulated in the private data. As we dont * treat different pid types differently we don't care for * now */ return PID_PRIVATE; break; case 7: /* ISO/IEC 13522 MHEG */ case 8: /* ITU-T Rec. H.220.0 / ISO/IEC 13818-1 Annex A DSM CC */ case 9: /* ITU-T Rec. H.220.1 */ case 10: /* ISO/IEC 13818-6 Type A */ case 11: /* ISO/IEC 13818-6 Type B */ case 12: /* ISO/IEC 13818-6 Type C */ case 13: /* ISO/IEC 13818-6 Type D */ case 14: /* ISO/IEC 13818-1 auxiliary */ return PID_OTHER; break; default: if (type & 0x80) return PID_USER; } return PID_OTHER; } static uint16_t pmt_pcr(struct psisec_s *section) { return (section->data[PMT_PCR_OFF1] << 8 | section->data[PMT_PCR_OFF2]) & PID_MASK; } static unsigned int pmt_pinfo_len(struct psisec_s *section) { return (section->data[PMT_PILEN_OFF1] << 8 | section->data[PMT_PILEN_OFF2]) & PMT_PILEN_MASK; } #define ESINFO_LEN_OFF 3 #define ESINFO_LEN_MASK 0x0fff #define ESINFO_MIN_LEN 5 static int pmt_es_infolen(struct psisec_s *section, int es) { return (section->data[es+ESINFO_LEN_OFF]<<8 | section->data[es+ESINFO_LEN_OFF+1]) & ESINFO_LEN_MASK; } static int pmt_next_es(struct psisec_s *section, int es) { int nes; nes=es+pmt_es_infolen(section, es)+ESINFO_MIN_LEN; /* Next offset beyond PMT end ? */ if (nes >= psi_len(section)-CRC32_LEN) return 0; return nes; } static int pmt_first_es(struct psisec_s *section) { return PMT_PI_OFF+pmt_pinfo_len(section); } static uint8_t pmt_es_type(struct psisec_s *section, int es) { return section->data[es]; } #define ES_PID_OFF 1 static unsigned int pmt_es_pid(struct psisec_s *section, int es) { return ((section->data[es+ES_PID_OFF]<<8 | section->data[es+ES_PID_OFF+1]) & PID_MASK); } unsigned int pmt_get_pmtpid(void *pvoid) { struct program_s *prog=pvoid; return prog->pmtpid; } struct pmt_s *pmt_new(void ) { return calloc(1, sizeof(struct pmt_s)); } void pmt_free(struct pmt_s *pmt) { GList *es=g_list_first(pmt->es); while(es) { g_free(es->data); es=g_list_next(es); } g_list_free(pmt->es); free(pmt); } void pmt_add_es(struct pmt_s *pmt, uint8_t type, uint16_t pid) { struct pmtes_s *es=g_new(struct pmtes_s, 1); es->streamtype=type; es->espid=pid; pmt->es=g_list_append(pmt->es, es); } static struct pmt_s *pmt_parse(struct program_s *prog) { struct pmt_s *pmt; struct psisec_s *s; int lastsecnum; int secnum; int esoff; int es=0; if (!prog->psi.section[0]) return NULL; pmt=pmt_new(); lastsecnum=psi_last_section_number(prog->psi.section[0]); for(secnum=0;secnum<=lastsecnum;secnum++) { s=prog->psi.section[secnum]; if (!s) continue; /* * FIXME - Multiple section PMTs might be inconsistent * concerning the PCR pid */ if (pmt_pcr(s) != PID_MAX) pmt->pcrpid=pmt_pcr(s); /* Walk PMT and add ES PIDS as necessary */ esoff=pmt_first_es(s); do { uint8_t type=pmt_es_type(s, esoff); uint16_t pid=pmt_es_pid(s, esoff); pmt_add_es(pmt, type, pid); es++; } while((esoff=pmt_next_es(s, esoff)) != 0); } logwrite(LOG_DEBUG, "pmt: parse_pmt found %d elementary streams for pnr %04x", es, prog->pnr); return pmt; } static struct pmtes_s *pmt_find_es(struct pmt_s *pmt, uint16_t pid) { GList *esl; struct pmtes_s *es; for(esl=g_list_first(pmt->es);esl;esl=g_list_next(esl)) { es=esl->data; if (es->espid == pid) return es; } return NULL; } static void pmt_update(struct program_s *prog) { struct pmt_s *current=prog->pmtcurrent; struct pmt_s *last=prog->pmtlast; GList *esl; struct pmtes_s *escur, *eslast; int i; logwrite(LOG_DEBUG, "pmt: pmt_update running for program %04x", prog->pnr); for(esl=g_list_first(current->es);esl;esl=g_list_next(esl)) { escur=esl->data; if (last) { eslast=pmt_find_es(last, escur->espid); if (eslast) continue; } if (!pmt_prog_gets_pid(prog, escur->espid)) pmt_prog_join_pid(prog, escur->espid, pmt_mapstreamtype(escur->streamtype)); } /* * We check if we get the PCR pid already. It seems most programs * multiplex the PCR informations into the Audio PID instead * a seperate PID so there would be no need to add a seperate callback * */ if (current->pcrpid != PID_MAX) { if (!pmt_prog_gets_pid(prog, current->pcrpid)) pmt_prog_join_pid(prog, current->pcrpid, PID_PCR); } /* * Walk all joined pids and check whether they are still active. * If they are not active - leave */ for(i=0;ipcrpid == i) continue; if (prog->pmtpid == i) continue; if (pmt_find_es(current, i)) continue; pmt_prog_leave_pid(prog, i); } } } static void pmt_dvr_pmt_cb(void *data, void *arg) { struct psisec_s *section=data; struct program_s *prog=arg; struct pmt_s *pmt; logwrite(LOG_XTREME, "pmt: dvr section callback"); dump_hex(LOG_XTREME, "pmt: PMT ", section->data, section->valid); if (!psi_currentnext(section)) return; if (psi_tableid(section) != PMT_TABLE_ID) { logwrite(LOG_INFO, "pmt: received PMT with broken table id on pid %d", prog->pmtpid); return; } if (pmt_pnr(section) != prog->pnr) { logwrite(LOG_DEBUG, "pmt: received PMT section for pnr %04x expected %04x", pmt_pnr(section), prog->pnr); return; } if (!psi_update_table(&prog->psi, section)) return; pmt=pmt_parse(prog); if (prog->pmtlast) pmt_free(prog->pmtlast); prog->pmtlast=prog->pmtcurrent; prog->pmtcurrent=pmt; pmt_update(prog); } /* Find program structure for a given PNR (Program Number) */ static struct program_s *pmt_prog_from_pnr(struct adapter_s *a, unsigned int pnr) { struct program_s *prog; GList *pl=g_list_first(a->pmt.pnrlist); /* Find pnr struct for this pnr */ while(pl) { prog=pl->data; if (prog->pnr == pnr) return prog; pl=g_list_next(pl); } return NULL; } /* * Callback from PAT parsing - Passes in adapter, pnr and pmtpid - If * we want this PNR and we didnt already join the PMT do so - In case * of a PMT pid change - leave old pid and join new one. * * In case of a PMTPID of 0 the PAT did not contain our program anymore. * */ void pmt_pidfrompat(struct adapter_s *a, unsigned int pnr, unsigned int pmtpid) { struct program_s *prog; prog=pmt_prog_from_pnr(a, pnr); if (!prog) return; logwrite(LOG_XTREME, "pmt: pidfrompat found pnr %04x", pnr); if (prog->pmtpid == pmtpid) return; logwrite(LOG_XTREME, "pmt: pidfrompat pmt pid changed from %04x to %04x", prog->pmtpid, pmtpid); /* If we have an old one - leave it */ if (prog->pmtpid) { dvr_del_pcb(a, prog->pmtpid, prog->pmtpidcb); pmt_prog_leave_pid(prog, prog->pmtpid); } if (pmtpid) { /* Add a callback for the new pmt pid */ prog->pmtpidcb=dvr_add_pcb(a, pmtpid, DVRCB_SECTION, PID_PMT, pmt_dvr_pmt_cb, prog); pmt_prog_join_pid(prog, pmtpid, PID_PMT); } else { /* FIXME - The Program disappeared - we should leave all pids */ } prog->pmtpid=pmtpid; } static struct program_s *pmt_prog_new(struct adapter_s *a, unsigned int pnr) { struct program_s *prog; prog=calloc(1, sizeof(struct program_s)); if (!prog) return NULL; prog->pnr=pnr; prog->pmtpid=0; prog->adapter=a; a->pmt.pnrlist=g_list_append(a->pmt.pnrlist, prog); return prog; } static void pmt_prog_add_cb(struct program_s *prog, void (*callback)(void *data, void *arg), void *arg) { struct programcb_s *pcb; pcb=malloc(sizeof(struct programcb_s)); pcb->callback=callback; pcb->arg=arg; prog->progcbl=g_list_append(prog->progcbl, pcb); } /* * Called from input_pnr to join a program number by supplying an * callback which will be put into the demux table by the PMT parser * */ void *pmt_join_pnr(struct adapter_s *a, unsigned int pnr, void (*callback)(void *data, void *arg), void *arg) { struct program_s *prog; /* * Find this program - might be multiple output * streams for the same program */ prog=pmt_prog_from_pnr(a, pnr); /* If we havent got one - create it */ if (!prog) prog=pmt_prog_new(a, pnr); /* Add callback to this stream to program list */ if (prog) pmt_prog_add_cb(prog, callback, arg); pat_init(a); return prog; }