vdr  1.7.31
eit.c
Go to the documentation of this file.
1 /*
2  * eit.c: EIT section filter
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * Original version (as used in VDR before 1.3.0) written by
8  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
9  * Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
10  *
11  * $Id: eit.c 2.21 2012/08/25 11:13:00 kls Exp $
12  */
13 
14 #include "eit.h"
15 #include "epg.h"
16 #include "i18n.h"
17 #include "libsi/section.h"
18 #include "libsi/descriptor.h"
19 
20 #define VALID_TIME (31536000 * 2) // two years
21 
22 // --- cEIT ------------------------------------------------------------------
23 
24 class cEIT : public SI::EIT {
25 public:
26  cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus = false);
27  };
28 
29 cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus)
30 :SI::EIT(Data, false)
31 {
32  if (!CheckCRCAndParse())
33  return;
34 
35  time_t Now = time(NULL);
36  if (Now < VALID_TIME)
37  return; // we need the current time for handling PDC descriptors
38 
39  if (!Channels.Lock(false, 10))
40  return;
42  cChannel *channel = Channels.GetByChannelID(channelID, true);
43  if (!channel || EpgHandlers.IgnoreChannel(channel)) {
44  Channels.Unlock();
45  return;
46  }
47 
48  bool handledExternally = EpgHandlers.HandledExternally(channel);
49  cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
50 
51  bool Empty = true;
52  bool Modified = false;
53  time_t SegmentStart = 0;
54  time_t SegmentEnd = 0;
55  struct tm tm_r;
56  struct tm t = *localtime_r(&Now, &tm_r); // this initializes the time zone in 't'
57 
58  SI::EIT::Event SiEitEvent;
59  for (SI::Loop::Iterator it; eventLoop.getNext(SiEitEvent, it); ) {
60  if (EpgHandlers.HandleEitEvent(pSchedule, &SiEitEvent, Tid, getVersionNumber()))
61  continue; // an EPG handler has done all of the processing
62  time_t StartTime = SiEitEvent.getStartTime();
63  int Duration = SiEitEvent.getDuration();
64  // Drop bogus events - but keep NVOD reference events, where all bits of the start time field are set to 1, resulting in a negative number.
65  if (StartTime == 0 || StartTime > 0 && Duration == 0)
66  continue;
67  Empty = false;
68  if (!SegmentStart)
69  SegmentStart = StartTime;
70  SegmentEnd = StartTime + Duration;
71  cEvent *newEvent = NULL;
72  cEvent *rEvent = NULL;
73  cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
74  if (!pEvent || handledExternally) {
75  if (OnlyRunningStatus)
76  continue;
77  if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
78  continue;
79  // If we don't have that event yet, we create a new one.
80  // Otherwise we copy the information into the existing event anyway, because the data might have changed.
81  pEvent = newEvent = new cEvent(SiEitEvent.getEventId());
82  newEvent->SetStartTime(StartTime);
83  newEvent->SetDuration(Duration);
84  if (!handledExternally)
85  pSchedule->AddEvent(newEvent);
86  }
87  else {
88  // We have found an existing event, either through its event ID or its start time.
89  pEvent->SetSeen();
90  uchar TableID = max(pEvent->TableID(), uchar(0x4E)); // for backwards compatibility, table ids less than 0x4E are treated as if they were "present"
91  // If the new event has a higher table ID, let's skip it.
92  // The lower the table ID, the more "current" the information.
93  if (Tid > TableID)
94  continue;
95  // If the new event comes from the same table and has the same version number
96  // as the existing one, let's skip it to avoid unnecessary work.
97  // Unfortunately some stations (like, e.g. "Premiere") broadcast their EPG data on several transponders (like
98  // the actual Premiere transponder and the Sat.1/Pro7 transponder), but use different version numbers on
99  // each of them :-( So if one DVB card is tuned to the Premiere transponder, while an other one is tuned
100  // to the Sat.1/Pro7 transponder, events will keep toggling because of the bogus version numbers.
101  else if (Tid == TableID && pEvent->Version() == getVersionNumber())
102  continue;
103  EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
104  EpgHandlers.SetStartTime(pEvent, StartTime);
105  EpgHandlers.SetDuration(pEvent, Duration);
106  }
107  if (pEvent->TableID() > 0x4E) // for backwards compatibility, table ids less than 0x4E are never overwritten
108  pEvent->SetTableID(Tid);
109  if (Tid == 0x4E) { // we trust only the present/following info on the actual TS
110  if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning)
111  pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel);
112  }
113  if (OnlyRunningStatus) {
114  pEvent->SetVersion(0xFF); // we have already changed the table id above, so set the version to an invalid value to make sure the next full run will be executed
115  continue; // do this before setting the version, so that the full update can be done later
116  }
117  pEvent->SetVersion(getVersionNumber());
118 
119  int LanguagePreferenceShort = -1;
120  int LanguagePreferenceExt = -1;
121  bool UseExtendedEventDescriptor = false;
122  SI::Descriptor *d;
123  SI::ExtendedEventDescriptors *ExtendedEventDescriptors = NULL;
124  SI::ShortEventDescriptor *ShortEventDescriptor = NULL;
125  cLinkChannels *LinkChannels = NULL;
126  cComponents *Components = NULL;
127  for (SI::Loop::Iterator it2; (d = SiEitEvent.eventDescriptors.getNext(it2)); ) {
128  switch (d->getDescriptorTag()) {
131  if (I18nIsPreferredLanguage(Setup.EPGLanguages, eed->languageCode, LanguagePreferenceExt) || !ExtendedEventDescriptors) {
132  delete ExtendedEventDescriptors;
133  ExtendedEventDescriptors = new SI::ExtendedEventDescriptors;
134  UseExtendedEventDescriptor = true;
135  }
136  if (UseExtendedEventDescriptor) {
137  ExtendedEventDescriptors->Add(eed);
138  d = NULL; // so that it is not deleted
139  }
140  if (eed->getDescriptorNumber() == eed->getLastDescriptorNumber())
141  UseExtendedEventDescriptor = false;
142  }
143  break;
146  if (I18nIsPreferredLanguage(Setup.EPGLanguages, sed->languageCode, LanguagePreferenceShort) || !ShortEventDescriptor) {
147  delete ShortEventDescriptor;
148  ShortEventDescriptor = sed;
149  d = NULL; // so that it is not deleted
150  }
151  }
152  break;
156  int NumContents = 0;
157  uchar Contents[MaxEventContents] = { 0 };
158  for (SI::Loop::Iterator it3; cd->nibbleLoop.getNext(Nibble, it3); ) {
159  if (NumContents < MaxEventContents) {
160  Contents[NumContents] = ((Nibble.getContentNibbleLevel1() & 0xF) << 4) | (Nibble.getContentNibbleLevel2() & 0xF);
161  NumContents++;
162  }
163  }
164  EpgHandlers.SetContents(pEvent, Contents);
165  }
166  break;
168  int LanguagePreferenceRating = -1;
171  for (SI::Loop::Iterator it3; prd->ratingLoop.getNext(Rating, it3); ) {
172  if (I18nIsPreferredLanguage(Setup.EPGLanguages, Rating.languageCode, LanguagePreferenceRating)) {
173  int ParentalRating = (Rating.getRating() & 0xFF);
174  switch (ParentalRating) {
175  // values defined by the DVB standard (minimum age = rating + 3 years):
176  case 0x01 ... 0x0F: ParentalRating += 3; break;
177  // values defined by broadcaster CSAT (now why didn't they just use 0x07, 0x09 and 0x0D?):
178  case 0x11: ParentalRating = 10; break;
179  case 0x12: ParentalRating = 12; break;
180  case 0x13: ParentalRating = 16; break;
181  default: ParentalRating = 0;
182  }
183  EpgHandlers.SetParentalRating(pEvent, ParentalRating);
184  }
185  }
186  }
187  break;
188  case SI::PDCDescriptorTag: {
190  t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
191  int month = t.tm_mon;
192  t.tm_mon = pd->getMonth() - 1;
193  t.tm_mday = pd->getDay();
194  t.tm_hour = pd->getHour();
195  t.tm_min = pd->getMinute();
196  t.tm_sec = 0;
197  if (month == 11 && t.tm_mon == 0) // current month is dec, but event is in jan
198  t.tm_year++;
199  else if (month == 0 && t.tm_mon == 11) // current month is jan, but event is in dec
200  t.tm_year--;
201  time_t vps = mktime(&t);
202  EpgHandlers.SetVps(pEvent, vps);
203  }
204  break;
207  cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, channel->Nid(), channel->Tid(), tsed->getReferenceServiceId()));
208  if (!rSchedule)
209  break;
210  rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
211  if (!rEvent)
212  break;
213  EpgHandlers.SetTitle(pEvent, rEvent->Title());
214  EpgHandlers.SetShortText(pEvent, rEvent->ShortText());
215  EpgHandlers.SetDescription(pEvent, rEvent->Description());
216  }
217  break;
220  tChannelID linkID(Source, ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
221  if (ld->getLinkageType() == 0xB0) { // Premiere World
222  bool hit = StartTime <= Now && Now < StartTime + Duration;
223  if (hit) {
224  char linkName[ld->privateData.getLength() + 1];
225  strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName));
226  // TODO is there a standard way to determine the character set of this string?
227  cChannel *link = Channels.GetByChannelID(linkID);
228  if (link != channel) { // only link to other channels, not the same one
229  //fprintf(stderr, "Linkage %s %4d %4d %5d %5d %5d %5d %02X '%s'\n", hit ? "*" : "", channel->Number(), link ? link->Number() : -1, SiEitEvent.getEventId(), ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId(), ld->getLinkageType(), linkName);//XXX
230  if (link) {
231  if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
232  link->SetName(linkName, "", "");
233  }
234  else if (Setup.UpdateChannels >= 4) {
235  cChannel *transponder = channel;
236  if (channel->Tid() != ld->getTransportStreamId())
237  transponder = Channels.GetByTransponderID(linkID);
238  link = Channels.NewChannel(transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
239  //XXX patFilter->Trigger();
240  }
241  if (link) {
242  if (!LinkChannels)
243  LinkChannels = new cLinkChannels;
244  LinkChannels->Add(new cLinkChannel(link));
245  }
246  }
247  else
248  channel->SetPortalName(linkName);
249  }
250  }
251  }
252  break;
255  uchar Stream = cd->getStreamContent();
256  uchar Type = cd->getComponentType();
257  if (1 <= Stream && Stream <= 6 && Type != 0) { // 1=MPEG2-video, 2=MPEG1-audio, 3=subtitles, 4=AC3-audio, 5=H.264-video, 6=HEAAC-audio
258  if (!Components)
259  Components = new cComponents;
260  char buffer[Utf8BufSize(256)];
261  Components->SetComponent(Components->NumComponents(), Stream, Type, I18nNormalizeLanguageCode(cd->languageCode), cd->description.getText(buffer, sizeof(buffer)));
262  }
263  }
264  break;
265  default: ;
266  }
267  delete d;
268  }
269 
270  if (!rEvent) {
271  if (ShortEventDescriptor) {
272  char buffer[Utf8BufSize(256)];
273  EpgHandlers.SetTitle(pEvent, ShortEventDescriptor->name.getText(buffer, sizeof(buffer)));
274  EpgHandlers.SetShortText(pEvent, ShortEventDescriptor->text.getText(buffer, sizeof(buffer)));
275  }
276  else {
277  EpgHandlers.SetTitle(pEvent, NULL);
278  EpgHandlers.SetShortText(pEvent, NULL);
279  }
280  if (ExtendedEventDescriptors) {
281  char buffer[Utf8BufSize(ExtendedEventDescriptors->getMaximumTextLength(": ")) + 1];
282  EpgHandlers.SetDescription(pEvent, ExtendedEventDescriptors->getText(buffer, sizeof(buffer), ": "));
283  }
284  else
285  EpgHandlers.SetDescription(pEvent, NULL);
286  }
287  delete ExtendedEventDescriptors;
288  delete ShortEventDescriptor;
289 
290  EpgHandlers.SetComponents(pEvent, Components);
291 
292  EpgHandlers.FixEpgBugs(pEvent);
293  if (LinkChannels)
294  channel->SetLinkChannels(LinkChannels);
295  Modified = true;
296  EpgHandlers.HandleEvent(pEvent);
297  if (handledExternally)
298  delete pEvent;
299  }
300  if (Tid == 0x4E) {
301  if (Empty && getSectionNumber() == 0)
302  // ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running
303  pSchedule->ClrRunningStatus(channel);
304  pSchedule->SetPresentSeen();
305  }
306  if (Modified && !OnlyRunningStatus) {
307  EpgHandlers.SortSchedule(pSchedule);
308  EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
309  Schedules->SetModified(pSchedule);
310  }
311  Channels.Unlock();
312 }
313 
314 // --- cTDT ------------------------------------------------------------------
315 
316 class cTDT : public SI::TDT {
317 private:
318  static cMutex mutex;
319  static int lastDiff;
320 public:
321  cTDT(const u_char *Data);
322  };
323 
325 int cTDT::lastDiff = 0;
326 
327 cTDT::cTDT(const u_char *Data)
328 :SI::TDT(Data, false)
329 {
330  CheckParse();
331 
332  time_t sattim = getTime();
333  time_t loctim = time(NULL);
334 
335  int diff = abs(sattim - loctim);
336  if (diff > 2) {
337  mutex.Lock();
338  if (abs(diff - lastDiff) < 3) {
339  if (stime(&sattim) == 0)
340  isyslog("system time changed from %s (%ld) to %s (%ld)", *TimeToString(loctim), loctim, *TimeToString(sattim), sattim);
341  else
342  esyslog("ERROR while setting system time: %m");
343  }
344  lastDiff = diff;
345  mutex.Unlock();
346  }
347 }
348 
349 // --- cEitFilter ------------------------------------------------------------
350 
351 time_t cEitFilter::disableUntil = 0;
352 
354 {
355  Set(0x12, 0x40, 0xC0); // event info now&next actual/other TS (0x4E/0x4F), future actual/other TS (0x5X/0x6X)
357  Set(0x14, 0x70); // TDT
358 }
359 
361 {
362  disableUntil = Time;
363 }
364 
365 void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
366 {
367  if (disableUntil) {
368  if (time(NULL) > disableUntil)
369  disableUntil = 0;
370  else
371  return;
372  }
373  switch (Pid) {
374  case 0x12: {
375  if (Tid >= 0x4E && Tid <= 0x6F) {
376  cSchedulesLock SchedulesLock(true, 10);
377  cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
378  if (Schedules)
379  cEIT EIT(Schedules, Source(), Tid, Data);
380  else {
381  // If we don't get a write lock, let's at least get a read lock, so
382  // that we can set the running status and 'seen' timestamp (well, actually
383  // with a read lock we shouldn't be doing that, but it's only integers that
384  // get changed, so it should be ok)
385  cSchedulesLock SchedulesLock;
386  cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
387  if (Schedules)
388  cEIT EIT(Schedules, Source(), Tid, Data, true);
389  }
390  }
391  }
392  break;
393  case 0x14: {
395  cTDT TDT(Data);
396  }
397  break;
398  default: ;
399  }
400 }