summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2021-04-04 11:06:30 +0200
committerKlaus Schmidinger <vdr@tvdr.de>2021-04-04 11:06:30 +0200
commitb80c22e9c4097220cf4a1e2cce82daeca1ba4a36 (patch)
treead8653e4511ae9fd96c1b69d6e873d2a122e5db4
parentf672fe90c1f5e0cbe0f6a0842b8ed0fe22d05a70 (diff)
downloadvdr-b80c22e9c4097220cf4a1e2cce82daeca1ba4a36.tar.gz
vdr-b80c22e9c4097220cf4a1e2cce82daeca1ba4a36.tar.bz2
Improved handling EPG data from the EIT tables
-rw-r--r--HISTORY16
-rw-r--r--eit.c98
-rw-r--r--eit.h20
-rw-r--r--epg.c49
-rw-r--r--epg.h9
5 files changed, 151 insertions, 41 deletions
diff --git a/HISTORY b/HISTORY
index 4e2e60fb..db58eb7e 100644
--- a/HISTORY
+++ b/HISTORY
@@ -9578,7 +9578,7 @@ Video Disk Recorder Revision History
given (reported by Manuel Reimer).
- Fixed handling $(PKG_CONFIG) in newplugin (thanks to Winfried Köhler).
-2021-03-17:
+2021-04-04:
- Fixed strreplace() to handle NULL strings (reported by Jürgen Schneider).
- Somewhere down the road the 'x' bit of Doxyfile.filter got lost, so the
@@ -9617,3 +9617,17 @@ Video Disk Recorder Revision History
- Decreased the scrambling timeout for CAMs known to decrypt a certain channel, so
that it won't collide with MAXBROKENTIMEOUT in recorder.c.
- Fixed scaling subtitles with anti-aliasing (thanks to Peter Bieringer).
+- Improved handling EPG data from the EIT tables:
+ + Table 0x4F is now completely ignored.
+ + Once a schedule has seen events from 0x5X, tables 0x6X are ignored for that
+ schedule.
+ + When looking up an event in its schedule, the start time is used for tables 0x6X, and the
+ event id for tables 0x4E and 0x5X.
+ + When hashing events by event id or start time, existing older entries in the hash
+ tables are now deleted before entering the new ones.
+ + The function cSchedule::GetEvent() is now deprecated and may be removed in a future
+ version. Use GetEventById() and GetEventByTime() instead.
+ + On channels that use proper event ids a change of the start time no longer
+ causes a new event to be created, but rather modifies the existing one. This
+ avoids possible interruptions in VPS recordings in case the event's start time
+ is changed while the recording is already going on.
diff --git a/eit.c b/eit.c
index e1f52993..404cd9dd 100644
--- a/eit.c
+++ b/eit.c
@@ -8,9 +8,26 @@
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
* Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
*
- * $Id: eit.c 5.1 2021/03/16 15:10:54 kls Exp $
+ * $Id: eit.c 5.2 2021/04/04 11:06:30 kls Exp $
*/
+// The various ways in which broadcasters handle (or screw up) their EPG:
+// - Some use the same version for all tables, and use unique event ids, which are the same in
+// both the 0x5X and the 0x6X tables. And once an event has an id, it keeps it until it is
+// no longer in the tables. Those are the good guys!
+// - Some use separate versions for each table (0x50, 0x51, ...).
+// - Some broadcast tables 0x5X and 0x6X, but use different event ids for the same event in both
+// sets of tables, and sometimes even use different titles, short texts or descriptions.
+// - Some broadcast the full EPG only on one transponder (tables 0x6X), and on the actual transponder
+// they provide only the present/following information.
+// - Some have overlapping events, especially when they mess up daylight saving time.
+// - Some use all new event ids every time they update their tables.
+// So, to bring order to chaos, VDR does as follows:
+// - Completely ignore table 0x4F.
+// - Once a schedule has seen events from 0x5X, tables 0x6X are ignored for that schedule.
+// - When looking up an event in its schedule, the start time is used for tables 0x6X, and the
+// event id for tables 0x4E and 0x5X.
+
#include "eit.h"
#include <sys/time.h>
#include "epg.h"
@@ -24,6 +41,13 @@
// --- cEitTables ------------------------------------------------------------
+cEitTables::cEitTables(void)
+{
+ complete = false;
+ tableStart = 0;
+ tableEnd = 0;
+}
+
bool cEitTables::Check(uchar TableId, uchar Version, int SectionNumber)
{
int ti = Index(TableId);
@@ -32,35 +56,38 @@ bool cEitTables::Check(uchar TableId, uchar Version, int SectionNumber)
bool cEitTables::Processed(uchar TableId, uchar LastTableId, int SectionNumber, int LastSectionNumber, int SegmentLastSectionNumber)
{
+ bool Result = false;
int ti = Index(TableId);
int LastIndex = Index(LastTableId);
+ complete = false;
if (sectionSyncer[ti].Processed(SectionNumber, LastSectionNumber, SegmentLastSectionNumber)) {
+ Result = true; // the table with TableId is complete
for (int i = 0; i <= LastIndex; i++) {
if (!sectionSyncer[i].Complete())
- return false;
+ return Result;
}
- return true; // all tables have been processed
+ complete = true; // all tables have been processed
}
- return false;
+ return Result;
}
// --- cEIT ------------------------------------------------------------------
class cEIT : public SI::EIT {
public:
- cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data);
+ cEIT(cEitTablesHash &EitTablesHash, int Source, u_char Tid, const u_char *Data);
};
-cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data)
+cEIT::cEIT(cEitTablesHash &EitTablesHash, int Source, u_char Tid, const u_char *Data)
:SI::EIT(Data, false)
{
if (!CheckCRCAndParse())
return;
int HashId = getServiceId();
- cEitTables *EitTables = SectionSyncerHash.Get(HashId);
+ cEitTables *EitTables = EitTablesHash.Get(HashId);
if (!EitTables) {
EitTables = new cEitTables;
- SectionSyncerHash.Add(EitTables, HashId);
+ EitTablesHash.Add(EitTables, HashId);
}
bool Process = EitTables->Check(Tid, getVersionNumber(), getSectionNumber());
if (Tid != 0x4E && !Process) // we need to set the 'seen' tag to watch the running status of the present/following event
@@ -98,10 +125,16 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
bool handledExternally = EpgHandlers.HandledExternally(Channel);
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true);
+ if (pSchedule->OnActualTp(Tid) && (Tid & 0xF0) == 0x60) {
+ SchedulesStateKey.Remove(false);
+ ChannelsStateKey.Remove(false);
+ return;
+ }
+
bool Empty = true;
bool Modified = false;
time_t LingerLimit = Now - Setup.EPGLinger * 60;
- time_t SegmentStart = 0;
+ time_t SegmentStart = 0; // these are actually "section" start/end times
time_t SegmentEnd = 0;
struct tm t = { 0 };
localtime_r(&Now, &t); // this initializes the time zone in 't'
@@ -122,9 +155,19 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
if (!SegmentStart)
SegmentStart = StartTime;
SegmentEnd = StartTime + Duration;
+ if (Tid == 0x4E) {
+ if (getSectionNumber() == 0)
+ EitTables->SetTableStart(SegmentStart);
+ else
+ EitTables->SetTableEnd(SegmentEnd);
+ }
cEvent *newEvent = NULL;
cEvent *rEvent = NULL;
- cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
+ cEvent *pEvent = NULL;
+ if (Tid == 0x4E || (Tid & 0xF0) == 0x50)
+ pEvent = const_cast<cEvent *>(pSchedule->GetEventById(SiEitEvent.getEventId()));
+ else
+ pEvent = const_cast<cEvent *>(pSchedule->GetEventByTime(StartTime));
if (!pEvent || handledExternally) {
if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
continue;
@@ -140,10 +183,13 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
// We have found an existing event, either through its event ID or its start time.
pEvent->SetSeen();
uchar TableID = max(pEvent->TableID(), uchar(0x4E)); // for backwards compatibility, table ids less than 0x4E are treated as if they were "present"
- // If the new event has a higher table ID, let's skip it.
- // The lower the table ID, the more "current" the information.
- if (Tid > TableID)
+ // We never overwrite present/following with events from other tables:
+ if (TableID == 0x4E && Tid != 0x4E)
continue;
+ if (pEvent->HasTimer()) {
+ if (pEvent->StartTime() != StartTime || pEvent->Duration() != Duration)
+ dsyslog("channel %d (%s) event %s times changed to %s-%s", Channel->Number(), Channel->Name(), *pEvent->ToDescr(), *TimeString(StartTime), *TimeString(StartTime + Duration));
+ }
EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
EpgHandlers.SetStartTime(pEvent, StartTime);
EpgHandlers.SetDuration(pEvent, Duration);
@@ -275,7 +321,7 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, Channel->Nid(), Channel->Tid(), tsed->getReferenceServiceId()));
if (!rSchedule)
break;
- rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
+ rEvent = (cEvent *)rSchedule->GetEventById(tsed->getReferenceEventId());
if (!rEvent)
break;
EpgHandlers.SetTitle(pEvent, rEvent->Title());
@@ -375,13 +421,17 @@ cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const
pSchedule->ClrRunningStatus(Channel);
pSchedule->SetPresentSeen();
}
- if (Modified) {
- EpgHandlers.SortSchedule(pSchedule);
- EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
- pSchedule->SetModified();
+ if (Process) {
+ bool Complete = EitTables->Processed(Tid, getLastTableId(), getSectionNumber(), getLastSectionNumber(), getSegmentLastSectionNumber());
+ if (Modified && (Tid >= 0x50 || Complete)) { // we process the 0x5X tables segment by segment, but 0x4E only if we have received ALL its segments (0 and 1, i.e. "present" and "following")
+ if (Tid == 0x4E && getLastSectionNumber() == 1) {
+ SegmentStart = EitTables->TableStart();
+ SegmentEnd = EitTables->TableEnd();
+ }
+ EpgHandlers.SortSchedule(pSchedule);
+ EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
+ }
}
- if (Process)
- EitTables->Processed(Tid, getLastTableId(), getSectionNumber(), getLastSectionNumber(), getSegmentLastSectionNumber());
SchedulesStateKey.Remove(Modified);
ChannelsStateKey.Remove(ChannelsModified);
EpgHandlers.EndSegmentTransfer(Modified);
@@ -443,7 +493,7 @@ time_t cEitFilter::disableUntil = 0;
cEitFilter::cEitFilter(void)
{
- Set(0x12, 0x40, 0xC0); // event info now&next actual/other TS (0x4E/0x4F), future actual/other TS (0x5X/0x6X)
+ Set(0x12, 0x40, 0xC0); // event info present&following actual/other TS (0x4E/0x4F), future actual/other TS (0x5X/0x6X)
Set(0x14, 0x70); // TDT
}
@@ -451,7 +501,7 @@ void cEitFilter::SetStatus(bool On)
{
cMutexLock MutexLock(&mutex);
cFilter::SetStatus(On);
- sectionSyncerHash.Clear();
+ eitTablesHash.Clear();
}
void cEitFilter::SetDisableUntil(time_t Time)
@@ -470,8 +520,8 @@ void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
}
switch (Pid) {
case 0x12: {
- if (Tid >= 0x4E && Tid <= 0x6F)
- cEIT EIT(sectionSyncerHash, Source(), Tid, Data);
+ if (Tid == 0x4E || Tid >= 0x50 && Tid <= 0x6F) // we ignore 0x4F, which only causes trouble
+ cEIT EIT(eitTablesHash, Source(), Tid, Data);
}
break;
case 0x14: {
diff --git a/eit.h b/eit.h
index aedf0b56..8beae026 100644
--- a/eit.h
+++ b/eit.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: eit.h 5.1 2021/03/16 15:10:54 kls Exp $
+ * $Id: eit.h 5.2 2021/04/04 11:06:30 kls Exp $
*/
#ifndef __EIT_H
@@ -17,30 +17,38 @@
// Event information (or EPG) is broadcast in tables 0x4E and 0x4F for "present/following" events on
// "this transponder" (0x4E) and "other transponders" (0x4F), as well as 0x50-0x5F ("all events on this
-// transponder) and 0x60-0x6F ("all events on other transponders). Since it's either "this" or "other",
+// transponder") and 0x60-0x6F ("all events on other transponders"). Since it's either "this" or "other",
// we only use one section syncer for 0x4E/0x4F and 16 syncers for either 0x5X or 0x6X.
class cEitTables : public cListObject {
private:
cSectionSyncerRandom sectionSyncer[NUM_EIT_TABLES]; // for tables 0x4E/0x4F and 0x50-0x5F/0x60-0x6F
+ time_t tableStart; // only used for table 0x4E
+ time_t tableEnd;
bool complete;
int Index(uchar TableId) { return (TableId < 0x50) ? 0 : (TableId & 0x0F) + 1; }
public:
- cEitTables(void) { complete = false; }
+ cEitTables(void);
+ void SetTableStart(time_t t) { tableStart = t; }
+ void SetTableEnd(time_t t) { tableEnd = t; }
+ time_t TableStart(void) { return tableStart; }
+ time_t TableEnd(void) { return tableEnd; }
bool Check(uchar TableId, uchar Version, int SectionNumber);
bool Processed(uchar TableId, uchar LastTableId, int SectionNumber, int LastSectionNumber, int SegmentLastSectionNumber = -1);
+ ///< Returns true if all sections of the table with the given TableId have been processed.
bool Complete(void) { return complete; }
+ ///< Returns true if all sections of all tables have been processed.
};
-class cSectionSyncerHash : public cHash<cEitTables> {
+class cEitTablesHash : public cHash<cEitTables> {
public:
- cSectionSyncerHash(void) : cHash(HASHSIZE, true) {};
+ cEitTablesHash(void) : cHash(HASHSIZE, true) {};
};
class cEitFilter : public cFilter {
private:
cMutex mutex;
- cSectionSyncerHash sectionSyncerHash;
+ cEitTablesHash eitTablesHash;
static time_t disableUntil;
protected:
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
diff --git a/epg.c b/epg.c
index c4080478..2aac8008 100644
--- a/epg.c
+++ b/epg.c
@@ -7,7 +7,7 @@
* Original version (as used in VDR before 1.3.0) written by
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
*
- * $Id: epg.c 5.1 2021/01/04 09:05:26 kls Exp $
+ * $Id: epg.c 5.2 2021/04/04 11:06:30 kls Exp $
*/
#include "epg.h"
@@ -547,7 +547,7 @@ bool cEvent::Read(FILE *f, cSchedule *Schedule, int &Line)
unsigned int Version = 0xFF; // actual value is ignored
int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
if (n >= 3 && n <= 5) {
- Event = (cEvent *)Schedule->GetEvent(EventID, StartTime);
+ Event = (cEvent *)Schedule->GetEventByTime(StartTime);
cEvent *newEvent = NULL;
if (Event)
DELETENULL(Event->components);
@@ -913,6 +913,7 @@ cSchedule::cSchedule(tChannelID ChannelID)
numTimers = 0;
hasRunning = false;
modified = 0;
+ onActualTp = false;
presentSeen = 0;
}
@@ -930,6 +931,13 @@ void cSchedule::DecNumTimers(void) const
numTimersMutex.Unlock();
}
+bool cSchedule::OnActualTp(uchar TableId)
+{
+ if ((TableId & 0xF0) == 0x50)
+ onActualTp = true;
+ return onActualTp;
+}
+
cEvent *cSchedule::AddEvent(cEvent *Event)
{
events.Add(Event);
@@ -942,15 +950,21 @@ void cSchedule::DelEvent(cEvent *Event)
{
if (Event->schedule == this) {
UnhashEvent(Event);
+ Event->schedule = NULL;
events.Del(Event);
}
}
void cSchedule::HashEvent(cEvent *Event)
{
+ if (cEvent *p = eventsHashID.Get(Event->EventID()))
+ eventsHashID.Del(p, p->EventID());
eventsHashID.Add(Event, Event->EventID());
- if (Event->StartTime() > 0) // 'StartTime < 0' is apparently used with NVOD channels
+ if (Event->StartTime() > 0) { // 'StartTime < 0' is apparently used with NVOD channels
+ if (cEvent *p = eventsHashStartTime.Get(Event->StartTime()))
+ eventsHashStartTime.Del(p, p->StartTime());
eventsHashStartTime.Add(Event, Event->StartTime());
+ }
}
void cSchedule::UnhashEvent(cEvent *Event)
@@ -990,6 +1004,7 @@ const cEvent *cSchedule::GetFollowingEvent(void) const
return p;
}
+#if DEPRECATED_SCHEDULE_GET_EVENT
const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const
{
// Returns the event info with the given StartTime or, if no actual StartTime
@@ -999,6 +1014,19 @@ const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const
else
return eventsHashID.Get(EventID);
}
+#endif
+
+const cEvent *cSchedule::GetEventById(tEventID EventID) const
+{
+ return eventsHashID.Get(EventID);
+}
+
+const cEvent *cSchedule::GetEventByTime(time_t StartTime) const
+{
+ if (StartTime > 0) // 'StartTime < 0' is apparently used with NVOD channels
+ return eventsHashStartTime.Get(StartTime);
+ return NULL;
+}
const cEvent *cSchedule::GetEventAround(time_t Time) const
{
@@ -1039,6 +1067,7 @@ void cSchedule::ClrRunningStatus(cChannel *Channel)
if (p->RunningStatus() >= SI::RunningStatusPausing) {
p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
hasRunning = false;
+ SetModified();
break;
}
}
@@ -1067,22 +1096,24 @@ void cSchedule::Sort(void)
void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
{
+ // Events are sorted by start time.
if (SegmentStart > 0 && SegmentEnd > 0) {
cEvent *p = events.First();
while (p) {
cEvent *n = events.Next(p);
- if (p->EndTime() > SegmentStart) {
+ if (p->StartTime() >= SegmentStart) {
if (p->StartTime() < SegmentEnd) {
- // The event overlaps with the given time segment.
- if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
- // The segment overwrites all events from tables with higher ids, and
+ // The event starts within the given time segment.
+ if ((p->TableID() > 0x4E || TableID == 0x4E) && (p->TableID() != TableID || p->Version() != Version)) {
+ // The segment overwrites all events from tables with other ids, and
// within the same table id all events must have the same version.
+ // Special consideration: table 0x4E can only be overwritten with the same id!
DelEvent(p);
}
}
- else
- break;
}
+ else
+ break;
p = n;
}
}
diff --git a/epg.h b/epg.h
index 6c422055..b3ed6201 100644
--- a/epg.h
+++ b/epg.h
@@ -7,7 +7,7 @@
* Original version (as used in VDR before 1.3.0) written by
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
*
- * $Id: epg.h 4.7 2017/05/28 12:59:20 kls Exp $
+ * $Id: epg.h 5.1 2021/04/04 11:06:30 kls Exp $
*/
#ifndef __EPG_H
@@ -156,12 +156,14 @@ private:
cHash<cEvent> eventsHashStartTime;
mutable u_int16_t numTimers;// The number of timers that use this schedule
bool hasRunning;
+ bool onActualTp;
int modified;
time_t presentSeen;
public:
cSchedule(tChannelID ChannelID);
tChannelID ChannelID(void) const { return channelID; }
bool Modified(int &State) const { bool Result = State != modified; State = modified; return Result; }
+ bool OnActualTp(uchar TableId);
time_t PresentSeen(void) const { return presentSeen; }
bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; }
void SetModified(void) { modified++; }
@@ -183,7 +185,12 @@ public:
const cList<cEvent> *Events(void) const { return &events; }
const cEvent *GetPresentEvent(void) const;
const cEvent *GetFollowingEvent(void) const;
+#define DEPRECATED_SCHEDULE_GET_EVENT 1
+#if DEPRECATED_SCHEDULE_GET_EVENT
const cEvent *GetEvent(tEventID EventID, time_t StartTime = 0) const;
+#endif
+ const cEvent *GetEventById(tEventID EventID) const;
+ const cEvent *GetEventByTime(time_t StartTime) const;
const cEvent *GetEventAround(time_t Time) const;
void Dump(const cChannels *Channels, FILE *f, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0) const;
static bool Read(FILE *f, cSchedules *Schedules);