vdr
1.7.27
|
00001 /* 00002 * pat.c: PAT section filter 00003 * 00004 * See the main source file 'vdr.c' for copyright information and 00005 * how to reach the author. 00006 * 00007 * $Id: pat.c 2.17 2012/03/02 10:56:45 kls Exp $ 00008 */ 00009 00010 #include "pat.h" 00011 #include <malloc.h> 00012 #include "channels.h" 00013 #include "libsi/section.h" 00014 #include "libsi/descriptor.h" 00015 #include "thread.h" 00016 #include "vdrttxtsubshooks.h" 00017 00018 #define PMT_SCAN_TIMEOUT 10 // seconds 00019 00020 // --- cCaDescriptor --------------------------------------------------------- 00021 00022 class cCaDescriptor : public cListObject { 00023 private: 00024 int caSystem; 00025 int esPid; 00026 int length; 00027 uchar *data; 00028 public: 00029 cCaDescriptor(int CaSystem, int CaPid, int EsPid, int Length, const uchar *Data); 00030 virtual ~cCaDescriptor(); 00031 bool operator== (const cCaDescriptor &arg) const; 00032 int CaSystem(void) { return caSystem; } 00033 int EsPid(void) { return esPid; } 00034 int Length(void) const { return length; } 00035 const uchar *Data(void) const { return data; } 00036 }; 00037 00038 cCaDescriptor::cCaDescriptor(int CaSystem, int CaPid, int EsPid, int Length, const uchar *Data) 00039 { 00040 caSystem = CaSystem; 00041 esPid = EsPid; 00042 length = Length + 6; 00043 data = MALLOC(uchar, length); 00044 data[0] = SI::CaDescriptorTag; 00045 data[1] = length - 2; 00046 data[2] = (caSystem >> 8) & 0xFF; 00047 data[3] = caSystem & 0xFF; 00048 data[4] = ((CaPid >> 8) & 0x1F) | 0xE0; 00049 data[5] = CaPid & 0xFF; 00050 if (Length) 00051 memcpy(&data[6], Data, Length); 00052 } 00053 00054 cCaDescriptor::~cCaDescriptor() 00055 { 00056 free(data); 00057 } 00058 00059 bool cCaDescriptor::operator== (const cCaDescriptor &arg) const 00060 { 00061 return esPid == arg.esPid && length == arg.length && memcmp(data, arg.data, length) == 0; 00062 } 00063 00064 // --- cCaDescriptors -------------------------------------------------------- 00065 00066 class cCaDescriptors : public cListObject { 00067 private: 00068 int source; 00069 int transponder; 00070 int serviceId; 00071 int numCaIds; 00072 int caIds[MAXCAIDS + 1]; 00073 cList<cCaDescriptor> caDescriptors; 00074 void AddCaId(int CaId); 00075 public: 00076 cCaDescriptors(int Source, int Transponder, int ServiceId); 00077 bool operator== (const cCaDescriptors &arg) const; 00078 bool Is(int Source, int Transponder, int ServiceId); 00079 bool Is(cCaDescriptors * CaDescriptors); 00080 bool Empty(void) { return caDescriptors.Count() == 0; } 00081 void AddCaDescriptor(SI::CaDescriptor *d, int EsPid); 00082 int GetCaDescriptors(const int *CaSystemIds, int BufSize, uchar *Data, int EsPid); 00083 const int *CaIds(void) { return caIds; } 00084 }; 00085 00086 cCaDescriptors::cCaDescriptors(int Source, int Transponder, int ServiceId) 00087 { 00088 source = Source; 00089 transponder = Transponder; 00090 serviceId = ServiceId; 00091 numCaIds = 0; 00092 caIds[0] = 0; 00093 } 00094 00095 bool cCaDescriptors::operator== (const cCaDescriptors &arg) const 00096 { 00097 cCaDescriptor *ca1 = caDescriptors.First(); 00098 cCaDescriptor *ca2 = arg.caDescriptors.First(); 00099 while (ca1 && ca2) { 00100 if (!(*ca1 == *ca2)) 00101 return false; 00102 ca1 = caDescriptors.Next(ca1); 00103 ca2 = arg.caDescriptors.Next(ca2); 00104 } 00105 return !ca1 && !ca2; 00106 } 00107 00108 bool cCaDescriptors::Is(int Source, int Transponder, int ServiceId) 00109 { 00110 return source == Source && transponder == Transponder && serviceId == ServiceId; 00111 } 00112 00113 bool cCaDescriptors::Is(cCaDescriptors *CaDescriptors) 00114 { 00115 return Is(CaDescriptors->source, CaDescriptors->transponder, CaDescriptors->serviceId); 00116 } 00117 00118 void cCaDescriptors::AddCaId(int CaId) 00119 { 00120 if (numCaIds < MAXCAIDS) { 00121 for (int i = 0; i < numCaIds; i++) { 00122 if (caIds[i] == CaId) 00123 return; 00124 } 00125 caIds[numCaIds++] = CaId; 00126 caIds[numCaIds] = 0; 00127 } 00128 } 00129 00130 void cCaDescriptors::AddCaDescriptor(SI::CaDescriptor *d, int EsPid) 00131 { 00132 cCaDescriptor *nca = new cCaDescriptor(d->getCaType(), d->getCaPid(), EsPid, d->privateData.getLength(), d->privateData.getData()); 00133 for (cCaDescriptor *ca = caDescriptors.First(); ca; ca = caDescriptors.Next(ca)) { 00134 if (*ca == *nca) { 00135 delete nca; 00136 return; 00137 } 00138 } 00139 AddCaId(nca->CaSystem()); 00140 caDescriptors.Add(nca); 00141 //#define DEBUG_CA_DESCRIPTORS 1 00142 #ifdef DEBUG_CA_DESCRIPTORS 00143 char buffer[1024]; 00144 char *q = buffer; 00145 q += sprintf(q, "CAM: %04X %5d %5d %04X %04X -", source, transponder, serviceId, d->getCaType(), EsPid); 00146 for (int i = 0; i < nca->Length(); i++) 00147 q += sprintf(q, " %02X", nca->Data()[i]); 00148 dsyslog("%s", buffer); 00149 #endif 00150 } 00151 00152 // EsPid is to select the "type" of CaDescriptor to be returned 00153 // >0 - CaDescriptor for the particular esPid 00154 // =0 - common CaDescriptor 00155 // <0 - all CaDescriptors regardless of type (old default) 00156 00157 int cCaDescriptors::GetCaDescriptors(const int *CaSystemIds, int BufSize, uchar *Data, int EsPid) 00158 { 00159 if (!CaSystemIds || !*CaSystemIds) 00160 return 0; 00161 if (BufSize > 0 && Data) { 00162 int length = 0; 00163 for (cCaDescriptor *d = caDescriptors.First(); d; d = caDescriptors.Next(d)) { 00164 if (EsPid < 0 || d->EsPid() == EsPid) { 00165 const int *caids = CaSystemIds; 00166 do { 00167 if (d->CaSystem() == *caids) { 00168 if (length + d->Length() <= BufSize) { 00169 memcpy(Data + length, d->Data(), d->Length()); 00170 length += d->Length(); 00171 } 00172 else 00173 return -1; 00174 } 00175 } while (*++caids); 00176 } 00177 } 00178 return length; 00179 } 00180 return -1; 00181 } 00182 00183 // --- cCaDescriptorHandler -------------------------------------------------- 00184 00185 class cCaDescriptorHandler : public cList<cCaDescriptors> { 00186 private: 00187 cMutex mutex; 00188 public: 00189 int AddCaDescriptors(cCaDescriptors *CaDescriptors); 00190 // Returns 0 if this is an already known descriptor, 00191 // 1 if it is an all new descriptor with actual contents, 00192 // and 2 if an existing descriptor was changed. 00193 int GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, int EsPid); 00194 }; 00195 00196 int cCaDescriptorHandler::AddCaDescriptors(cCaDescriptors *CaDescriptors) 00197 { 00198 cMutexLock MutexLock(&mutex); 00199 for (cCaDescriptors *ca = First(); ca; ca = Next(ca)) { 00200 if (ca->Is(CaDescriptors)) { 00201 if (*ca == *CaDescriptors) { 00202 delete CaDescriptors; 00203 return 0; 00204 } 00205 Del(ca); 00206 Add(CaDescriptors); 00207 return 2; 00208 } 00209 } 00210 Add(CaDescriptors); 00211 return CaDescriptors->Empty() ? 0 : 1; 00212 } 00213 00214 int cCaDescriptorHandler::GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, int EsPid) 00215 { 00216 cMutexLock MutexLock(&mutex); 00217 for (cCaDescriptors *ca = First(); ca; ca = Next(ca)) { 00218 if (ca->Is(Source, Transponder, ServiceId)) 00219 return ca->GetCaDescriptors(CaSystemIds, BufSize, Data, EsPid); 00220 } 00221 return 0; 00222 } 00223 00224 cCaDescriptorHandler CaDescriptorHandler; 00225 00226 int GetCaDescriptors(int Source, int Transponder, int ServiceId, const int *CaSystemIds, int BufSize, uchar *Data, int EsPid) 00227 { 00228 return CaDescriptorHandler.GetCaDescriptors(Source, Transponder, ServiceId, CaSystemIds, BufSize, Data, EsPid); 00229 } 00230 00231 // --- cPatFilter ------------------------------------------------------------ 00232 00233 cPatFilter::cPatFilter(void) 00234 { 00235 pmtIndex = 0; 00236 pmtPid = 0; 00237 pmtSid = 0; 00238 lastPmtScan = 0; 00239 numPmtEntries = 0; 00240 Set(0x00, 0x00); // PAT 00241 } 00242 00243 void cPatFilter::SetStatus(bool On) 00244 { 00245 cFilter::SetStatus(On); 00246 pmtIndex = 0; 00247 pmtPid = 0; 00248 pmtSid = 0; 00249 lastPmtScan = 0; 00250 numPmtEntries = 0; 00251 } 00252 00253 void cPatFilter::Trigger(void) 00254 { 00255 numPmtEntries = 0; 00256 } 00257 00258 bool cPatFilter::PmtVersionChanged(int PmtPid, int Sid, int Version) 00259 { 00260 uint64_t v = Version; 00261 v <<= 32; 00262 uint64_t id = (PmtPid | (Sid << 16)) & 0x00000000FFFFFFFFLL; 00263 for (int i = 0; i < numPmtEntries; i++) { 00264 if ((pmtVersion[i] & 0x00000000FFFFFFFFLL) == id) { 00265 bool Changed = (pmtVersion[i] & 0x000000FF00000000LL) != v; 00266 if (Changed) 00267 pmtVersion[i] = id | v; 00268 return Changed; 00269 } 00270 } 00271 if (numPmtEntries < MAXPMTENTRIES) 00272 pmtVersion[numPmtEntries++] = id | v; 00273 return true; 00274 } 00275 00276 void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) 00277 { 00278 if (Pid == 0x00) { 00279 if (Tid == 0x00) { 00280 if (pmtPid && time(NULL) - lastPmtScan > PMT_SCAN_TIMEOUT) { 00281 Del(pmtPid, 0x02); 00282 pmtPid = 0; 00283 pmtIndex++; 00284 lastPmtScan = time(NULL); 00285 } 00286 if (!pmtPid) { 00287 SI::PAT pat(Data, false); 00288 if (!pat.CheckCRCAndParse()) 00289 return; 00290 SI::PAT::Association assoc; 00291 int Index = 0; 00292 for (SI::Loop::Iterator it; pat.associationLoop.getNext(assoc, it); ) { 00293 if (!assoc.isNITPid()) { 00294 if (Index++ >= pmtIndex && Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId())) { 00295 pmtPid = assoc.getPid(); 00296 pmtSid = assoc.getServiceId(); 00297 Add(pmtPid, 0x02); 00298 break; 00299 } 00300 } 00301 } 00302 if (!pmtPid) 00303 pmtIndex = 0; 00304 } 00305 } 00306 } 00307 else if (Pid == pmtPid && Tid == SI::TableIdPMT && Source() && Transponder()) { 00308 SI::PMT pmt(Data, false); 00309 if (!pmt.CheckCRCAndParse()) 00310 return; 00311 if (pmt.getServiceId() != pmtSid) 00312 return; // skip broken PMT records 00313 if (!PmtVersionChanged(pmtPid, pmt.getTableIdExtension(), pmt.getVersionNumber())) { 00314 lastPmtScan = 0; // this triggers the next scan 00315 return; 00316 } 00317 if (!Channels.Lock(true, 10)) { 00318 numPmtEntries = 0; // to make sure we try again 00319 return; 00320 } 00321 cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), pmt.getServiceId()); 00322 if (Channel) { 00323 SI::CaDescriptor *d; 00324 cCaDescriptors *CaDescriptors = new cCaDescriptors(Channel->Source(), Channel->Transponder(), Channel->Sid()); 00325 // Scan the common loop: 00326 for (SI::Loop::Iterator it; (d = (SI::CaDescriptor*)pmt.commonDescriptors.getNext(it, SI::CaDescriptorTag)); ) { 00327 CaDescriptors->AddCaDescriptor(d, 0); 00328 delete d; 00329 } 00330 // Scan the stream-specific loop: 00331 SI::PMT::Stream stream; 00332 int Vpid = 0; 00333 int Ppid = 0; 00334 int Vtype = 0; 00335 int Apids[MAXAPIDS + 1] = { 0 }; // these lists are zero-terminated 00336 int Atypes[MAXAPIDS + 1] = { 0 }; 00337 int Dpids[MAXDPIDS + 1] = { 0 }; 00338 int Dtypes[MAXDPIDS + 1] = { 0 }; 00339 int Spids[MAXSPIDS + 1] = { 0 }; 00340 uchar SubtitlingTypes[MAXSPIDS + 1] = { 0 }; 00341 uint16_t CompositionPageIds[MAXSPIDS + 1] = { 0 }; 00342 uint16_t AncillaryPageIds[MAXSPIDS + 1] = { 0 }; 00343 char ALangs[MAXAPIDS][MAXLANGCODE2] = { "" }; 00344 char DLangs[MAXDPIDS][MAXLANGCODE2] = { "" }; 00345 char SLangs[MAXSPIDS][MAXLANGCODE2] = { "" }; 00346 int Tpid = 0; 00347 tTeletextSubtitlePage TeletextSubtitlePages[MAXTXTPAGES]; 00348 int NumTPages = 0; 00349 int NumApids = 0; 00350 int NumDpids = 0; 00351 int NumSpids = 0; 00352 for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); ) { 00353 bool ProcessCaDescriptors = false; 00354 int esPid = stream.getPid(); 00355 switch (stream.getStreamType()) { 00356 case 1: // STREAMTYPE_11172_VIDEO 00357 case 2: // STREAMTYPE_13818_VIDEO 00358 case 0x1B: // MPEG4 00359 Vpid = esPid; 00360 Ppid = pmt.getPCRPid(); 00361 Vtype = stream.getStreamType(); 00362 ProcessCaDescriptors = true; 00363 break; 00364 case 3: // STREAMTYPE_11172_AUDIO 00365 case 4: // STREAMTYPE_13818_AUDIO 00366 case 0x0F: // ISO/IEC 13818-7 Audio with ADTS transport syntax 00367 case 0x11: // ISO/IEC 14496-3 Audio with LATM transport syntax 00368 { 00369 if (NumApids < MAXAPIDS) { 00370 Apids[NumApids] = esPid; 00371 Atypes[NumApids] = stream.getStreamType(); 00372 SI::Descriptor *d; 00373 for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { 00374 switch (d->getDescriptorTag()) { 00375 case SI::ISO639LanguageDescriptorTag: { 00376 SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; 00377 SI::ISO639LanguageDescriptor::Language l; 00378 char *s = ALangs[NumApids]; 00379 int n = 0; 00380 for (SI::Loop::Iterator it; ld->languageLoop.getNext(l, it); ) { 00381 if (*ld->languageCode != '-') { // some use "---" to indicate "none" 00382 if (n > 0) 00383 *s++ = '+'; 00384 strn0cpy(s, I18nNormalizeLanguageCode(l.languageCode), MAXLANGCODE1); 00385 s += strlen(s); 00386 if (n++ > 1) 00387 break; 00388 } 00389 } 00390 } 00391 break; 00392 default: ; 00393 } 00394 delete d; 00395 } 00396 NumApids++; 00397 } 00398 ProcessCaDescriptors = true; 00399 } 00400 break; 00401 case 5: // STREAMTYPE_13818_PRIVATE 00402 case 6: // STREAMTYPE_13818_PES_PRIVATE 00403 //XXX case 8: // STREAMTYPE_13818_DSMCC 00404 { 00405 int dpid = 0; 00406 int dtype = 0; 00407 char lang[MAXLANGCODE1] = { 0 }; 00408 SI::Descriptor *d; 00409 for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { 00410 switch (d->getDescriptorTag()) { 00411 case SI::AC3DescriptorTag: 00412 case SI::EnhancedAC3DescriptorTag: 00413 dpid = esPid; 00414 dtype = d->getDescriptorTag(); 00415 ProcessCaDescriptors = true; 00416 break; 00417 case SI::SubtitlingDescriptorTag: 00418 if (NumSpids < MAXSPIDS) { 00419 Spids[NumSpids] = esPid; 00420 SI::SubtitlingDescriptor *sd = (SI::SubtitlingDescriptor *)d; 00421 SI::SubtitlingDescriptor::Subtitling sub; 00422 char *s = SLangs[NumSpids]; 00423 int n = 0; 00424 for (SI::Loop::Iterator it; sd->subtitlingLoop.getNext(sub, it); ) { 00425 if (sub.languageCode[0]) { 00426 SubtitlingTypes[NumSpids] = sub.getSubtitlingType(); 00427 CompositionPageIds[NumSpids] = sub.getCompositionPageId(); 00428 AncillaryPageIds[NumSpids] = sub.getAncillaryPageId(); 00429 if (n > 0) 00430 *s++ = '+'; 00431 strn0cpy(s, I18nNormalizeLanguageCode(sub.languageCode), MAXLANGCODE1); 00432 s += strlen(s); 00433 if (n++ > 1) 00434 break; 00435 } 00436 } 00437 NumSpids++; 00438 } 00439 break; 00440 case SI::TeletextDescriptorTag: { 00441 Tpid = esPid; 00442 SI::TeletextDescriptor *sd = (SI::TeletextDescriptor *)d; 00443 SI::TeletextDescriptor::Teletext ttxt; 00444 for (SI::Loop::Iterator it; sd->teletextLoop.getNext(ttxt, it); ) { 00445 bool isSubtitlePage = (ttxt.getTeletextType() == 0x02) || (ttxt.getTeletextType() == 0x05); 00446 if ((NumTPages < MAXTXTPAGES) && ttxt.languageCode[0] && isSubtitlePage) { 00447 strn0cpy(TeletextSubtitlePages[NumTPages].ttxtLanguage, I18nNormalizeLanguageCode(ttxt.languageCode), MAXLANGCODE1); 00448 TeletextSubtitlePages[NumTPages].ttxtPage = ttxt.getTeletextPageNumber(); 00449 TeletextSubtitlePages[NumTPages].ttxtMagazine = ttxt.getTeletextMagazineNumber(); 00450 TeletextSubtitlePages[NumTPages].ttxtType = ttxt.getTeletextType(); 00451 NumTPages++; 00452 } 00453 } 00454 } 00455 break; 00456 case SI::ISO639LanguageDescriptorTag: { 00457 SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; 00458 strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); 00459 } 00460 break; 00461 default: ; 00462 } 00463 delete d; 00464 } 00465 if (dpid) { 00466 if (NumDpids < MAXDPIDS) { 00467 Dpids[NumDpids] = dpid; 00468 Dtypes[NumDpids] = dtype; 00469 strn0cpy(DLangs[NumDpids], lang, MAXLANGCODE1); 00470 NumDpids++; 00471 } 00472 } 00473 } 00474 break; 00475 case 0x80: // STREAMTYPE_USER_PRIVATE - DigiCipher II VIDEO (ANSI/SCTE 57) 00476 Vpid = esPid; 00477 Ppid = pmt.getPCRPid(); 00478 Vtype = 0x02; // compression based upon MPEG-2 00479 ProcessCaDescriptors = true; 00480 break; 00481 case 0x81: // STREAMTYPE_USER_PRIVATE - ATSC A/53 AUDIO (ANSI/SCTE 57) 00482 { 00483 char lang[MAXLANGCODE1] = { 0 }; 00484 SI::Descriptor *d; 00485 for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { 00486 switch (d->getDescriptorTag()) { 00487 case SI::ISO639LanguageDescriptorTag: { 00488 SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; 00489 strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); 00490 } 00491 break; 00492 default: ; 00493 } 00494 delete d; 00495 } 00496 if (NumDpids < MAXDPIDS) { 00497 Dpids[NumDpids] = esPid; 00498 Dtypes[NumDpids] = SI::AC3DescriptorTag; 00499 strn0cpy(DLangs[NumDpids], lang, MAXLANGCODE1); 00500 NumDpids++; 00501 } 00502 ProcessCaDescriptors = true; 00503 } 00504 break; 00505 case 0x82 ... 0xFF: // STREAMTYPE_USER_PRIVATE 00506 { 00507 char lang[MAXLANGCODE1] = { 0 }; 00508 bool IsAc3 = false; 00509 SI::Descriptor *d; 00510 for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { 00511 switch (d->getDescriptorTag()) { 00512 case SI::RegistrationDescriptorTag: { 00513 SI::RegistrationDescriptor *rd = (SI::RegistrationDescriptor *)d; 00514 // http://www.smpte-ra.org/mpegreg/mpegreg.html 00515 switch (rd->getFormatIdentifier()) { 00516 case 0x41432D33: // 'AC-3' 00517 IsAc3 = true; 00518 break; 00519 default: 00520 //printf("Format identifier: 0x%08X (pid: %d)\n", rd->getFormatIdentifier(), esPid); 00521 break; 00522 } 00523 } 00524 break; 00525 case SI::ISO639LanguageDescriptorTag: { 00526 SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; 00527 strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); 00528 } 00529 break; 00530 default: ; 00531 } 00532 delete d; 00533 } 00534 if (IsAc3) { 00535 if (NumDpids < MAXDPIDS) { 00536 Dpids[NumDpids] = esPid; 00537 Dtypes[NumDpids] = SI::AC3DescriptorTag; 00538 strn0cpy(DLangs[NumDpids], lang, MAXLANGCODE1); 00539 NumDpids++; 00540 } 00541 ProcessCaDescriptors = true; 00542 } 00543 } 00544 break; 00545 default: ;//printf("PID: %5d %5d %2d %3d %3d\n", pmt.getServiceId(), stream.getPid(), stream.getStreamType(), pmt.getVersionNumber(), Channel->Number()); 00546 } 00547 if (ProcessCaDescriptors) { 00548 for (SI::Loop::Iterator it; (d = (SI::CaDescriptor*)stream.streamDescriptors.getNext(it, SI::CaDescriptorTag)); ) { 00549 CaDescriptors->AddCaDescriptor(d, esPid); 00550 delete d; 00551 } 00552 } 00553 } 00554 if (Setup.UpdateChannels >= 2) { 00555 Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid); 00556 if (NumTPages < MAXTXTPAGES) { 00557 int manualPageNumber = cVDRTtxtsubsHookListener::Hook()->ManualPageNumber(Channel); 00558 if (manualPageNumber) 00559 TeletextSubtitlePages[NumTPages++] = tTeletextSubtitlePage(manualPageNumber); 00560 } 00561 Channel->SetTeletextSubtitlePages(TeletextSubtitlePages, NumTPages); 00562 Channel->SetCaIds(CaDescriptors->CaIds()); 00563 Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds); 00564 } 00565 Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors)); 00566 } 00567 lastPmtScan = 0; // this triggers the next scan 00568 Channels.Unlock(); 00569 } 00570 }