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