vdr/eit.c
Klaus Schmidinger 939081e274 Version 1.7.11
- Fixed resetting the file size when regenerating the index file.
- The new function cDevice::PatPmtParser() can be used in derived devices to access
  the PAT/PMT of the currently replayed material.
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- The PCR pid in generated PMTs is now set to 0x1FFF ("no PCR pid") in
  cPatPmtGenerator::GeneratePmt(), because VDR doesn't record the PCR pid.
- Updated the Estonian OSD texts (thanks to Arthur Konovalov).
- The 'sky' plugin is no longer part of the VDR source.
- Improved SPU handling on devices with limited OSD capabilities (thanks to
  Matthieu Castet).
- Several code modifications to avoid compiler warnings (thanks to Winfried Köhler).
- Added stream type 11172 AUDIO to cPatPmtParser::ParsePmt() (thanks to Johann
  Friedrichs).
- Removed debug output of '-' from cTransfer::Receive().
- Added defines for large files to the 'newplugin' script (reported by Udo Richter).
- Removed the workaround for short channel names of "Kabel Deutschland", because
  apparently they now have their data according to the DVB standard (thanks to
  Johann Friedrichs).
- Some fixes to dvbspu.[hc] (thanks to Johann Friedrichs).
- Fixed a busy loop when moving editing marks (thanks to Johann Friedrichs).
- Updated sources.conf (thanks to Derek Kelly).
- Modified cCharSetConv so that it can be used to convert from "whatever VDR uses"
  to a given code (thanks to Joachim Wilke).
- Channel names containing commas are now handled correctly in channels.conf.
  If a channel's short name contains a comma, it is replaced with a '.'.
- cDevice now logs the device number when a new device is created.
- Fixed handling STREAMTYPE_11172_AUDIO in cPatPmtParser::ParsePmt().
- cParsePatPmt now has functions to retrieve the audio, dolby and subtitle pids.
- cPatFilter::Process() now only stores CA descriptors for video and audio pids
  (thanks to Francesco Saverio Schiavarelli for reporting a problem with channels
  that have some encrypted components that VDR doesn't use).
- cDevice::AddPid() now stores the stream type of the given pid (thanks to Andreas
  Regel).
- Added cFont::FontName() and cFont::Size() (thanks to Andreas Regel).
- cPatPmtParser now also stores the audio stream types.
- The support for full featured DVB cards of the TT/FuSi design has been moved
  into the new plugin 'dvbsddevice'. On systems that use such a card as their
  primary device, this plugin now needs to be loaded when running VDR in order
  to view live or recorded video. If the plugin is not loaded, the card will
  be treated like a budget DVB card, and there will be no OSD or viewing
  capability.
- Fixed handling the "CA PMT" generation (revised a change not mentioned in version
  1.7.9's changes, which caused a malfunction with Conax and Viaccess CAMs).
- Fixed stopping subtitle display when switching the primary device (thanks to
  Anssi Hannula).
  IMPORTANT NOTE TO PLUGIN AUTHORS: a plugin that implements a derived cDevice
  class that can replay video must now call the MakePrimaryDevice() function of
  its base class.
- Fixed compiler warnings "format not a string literal and no format arguments"
  in some syslog calls (thanks to Rolf Ahrenberg).
- The new command line options --edit and --genindex can be used to edit a
  recording or generate its index without actually starting the entire VDR
  (based on a patch from Helmut Auer).
- Improved the description of the transponder parameters in vdr.5 (thanks to
  Winfried Köhler).
- Avoiding setting the video stream type to 2 if the vpid is 0 (problem reported
  by Arthur Konovalov).
- Implemented handling the "Content Descriptor" (based on a patch from Rolf
  Ahrenberg). The 'classic', 'sttng' and 'curses' skins display the textual
  representation of the content descriptors as "genre". The epg.data file stores
  the genre using the tag character 'G'.
- Implemented handling the "Parental Rating Descriptor" (based on a patch from Rolf
  Ahrenberg). The 'classic', 'sttng' and 'curses' skins display the parental
  rating (if given) in their event displays. The epg.data file stores
  the parental rating using the tag character 'R'.
  IMPORTANT NOTE: if VDR doesn't display a parental rating, this does not
  necessarily mean that the given programme is suitable for all audiences!
- Rearranged cEvent members to minimize memory waste.
- After a CLRE command, no further EPG processing is now done for 10 seconds,
  so that data sent with subsequent PUTE commands doesn't interfere with data
  from the broadcasters (suggested by Helmut Auer).
- Added support for DVB cards with multiple fontends. Note that this only
  works for DVB cards where each frontend can be used independently of all
  the others on the same adapter.
- Fixed plugin arguments corruption with glibc 2.11 on x86_64 (thanks to
  Anssi Hannula).
2010-01-06 13:34:00 +01:00

404 lines
17 KiB
C

/*
* eit.c: EIT section filter
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* Original version (as used in VDR before 1.3.0) written by
* 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 2.10 2010/01/03 15:35:21 kls Exp $
*/
#include "eit.h"
#include "epg.h"
#include "i18n.h"
#include "libsi/section.h"
#include "libsi/descriptor.h"
#define VALID_TIME (31536000 * 2) // two years
// --- cEIT ------------------------------------------------------------------
class cEIT : public SI::EIT {
public:
cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus = false);
};
cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus)
:SI::EIT(Data, false)
{
if (!CheckCRCAndParse())
return;
tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId());
cChannel *channel = Channels.GetByChannelID(channelID, true);
if (!channel)
return; // only collect data for known channels
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
bool Empty = true;
bool Modified = false;
bool HasExternalData = false;
time_t SegmentStart = 0;
time_t SegmentEnd = 0;
time_t Now = time(NULL);
struct tm tm_r;
struct tm t = *localtime_r(&Now, &tm_r); // this initializes the time zone in 't'
if (Now < VALID_TIME)
return; // we need the current time for handling PDC descriptors
SI::EIT::Event SiEitEvent;
for (SI::Loop::Iterator it; eventLoop.getNext(SiEitEvent, it); ) {
bool ExternalData = false;
time_t StartTime = SiEitEvent.getStartTime();
int Duration = SiEitEvent.getDuration();
// 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.
if (StartTime == 0 || StartTime > 0 && Duration == 0)
continue;
Empty = false;
if (!SegmentStart)
SegmentStart = StartTime;
SegmentEnd = StartTime + Duration;
cEvent *newEvent = NULL;
cEvent *rEvent = NULL;
cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
if (!pEvent) {
if (OnlyRunningStatus)
continue;
// If we don't have that event yet, we create a new one.
// Otherwise we copy the information into the existing event anyway, because the data might have changed.
pEvent = newEvent = new cEvent(SiEitEvent.getEventId());
if (!pEvent)
continue;
}
else {
// We have found an existing event, either through its event ID or its start time.
pEvent->SetSeen();
// If the existing event has a zero table ID it was defined externally and shall
// not be overwritten.
uchar TableID = pEvent->TableID();
if (TableID == 0x00) {
if (pEvent->Version() == getVersionNumber())
continue;
HasExternalData = ExternalData = true;
}
// If the new event has a higher table ID, let's skip it.
// The lower the table ID, the more "current" the information.
else if (Tid > TableID)
continue;
// If the new event comes from the same table and has the same version number
// as the existing one, let's skip it to avoid unnecessary work.
// Unfortunately some stations (like, e.g. "Premiere") broadcast their EPG data on several transponders (like
// the actual Premiere transponder and the Sat.1/Pro7 transponder), but use different version numbers on
// each of them :-( So if one DVB card is tuned to the Premiere transponder, while an other one is tuned
// to the Sat.1/Pro7 transponder, events will keep toggling because of the bogus version numbers.
else if (Tid == TableID && pEvent->Version() == getVersionNumber())
continue;
}
if (!ExternalData) {
pEvent->SetEventID(SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
pEvent->SetTableID(Tid);
pEvent->SetStartTime(StartTime);
pEvent->SetDuration(Duration);
}
if (newEvent)
pSchedule->AddEvent(newEvent);
if (Tid == 0x4E) { // we trust only the present/following info on the actual TS
if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning)
pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel);
}
if (OnlyRunningStatus)
continue; // do this before setting the version, so that the full update can be done later
pEvent->SetVersion(getVersionNumber());
int LanguagePreferenceShort = -1;
int LanguagePreferenceExt = -1;
bool UseExtendedEventDescriptor = false;
SI::Descriptor *d;
SI::ExtendedEventDescriptors *ExtendedEventDescriptors = NULL;
SI::ShortEventDescriptor *ShortEventDescriptor = NULL;
cLinkChannels *LinkChannels = NULL;
cComponents *Components = NULL;
for (SI::Loop::Iterator it2; (d = SiEitEvent.eventDescriptors.getNext(it2)); ) {
if (ExternalData && d->getDescriptorTag() != SI::ComponentDescriptorTag) {
delete d;
continue;
}
switch (d->getDescriptorTag()) {
case SI::ExtendedEventDescriptorTag: {
SI::ExtendedEventDescriptor *eed = (SI::ExtendedEventDescriptor *)d;
if (I18nIsPreferredLanguage(Setup.EPGLanguages, eed->languageCode, LanguagePreferenceExt) || !ExtendedEventDescriptors) {
delete ExtendedEventDescriptors;
ExtendedEventDescriptors = new SI::ExtendedEventDescriptors;
UseExtendedEventDescriptor = true;
}
if (UseExtendedEventDescriptor) {
ExtendedEventDescriptors->Add(eed);
d = NULL; // so that it is not deleted
}
if (eed->getDescriptorNumber() == eed->getLastDescriptorNumber())
UseExtendedEventDescriptor = false;
}
break;
case SI::ShortEventDescriptorTag: {
SI::ShortEventDescriptor *sed = (SI::ShortEventDescriptor *)d;
if (I18nIsPreferredLanguage(Setup.EPGLanguages, sed->languageCode, LanguagePreferenceShort) || !ShortEventDescriptor) {
delete ShortEventDescriptor;
ShortEventDescriptor = sed;
d = NULL; // so that it is not deleted
}
}
break;
case SI::ContentDescriptorTag: {
SI::ContentDescriptor *cd = (SI::ContentDescriptor *)d;
SI::ContentDescriptor::Nibble Nibble;
int NumContents = 0;
uchar Contents[MAXEVCONTENTS] = { 0 };
for (SI::Loop::Iterator it3; cd->nibbleLoop.getNext(Nibble, it3); ) {
if (NumContents < MAXEVCONTENTS) {
Contents[NumContents] = ((Nibble.getContentNibbleLevel1() & 0xF) << 4) | (Nibble.getContentNibbleLevel2() & 0xF);
NumContents++;
}
}
pEvent->SetContents(Contents);
}
break;
case SI::ParentalRatingDescriptorTag: {
int LanguagePreferenceRating = -1;
SI::ParentalRatingDescriptor *prd = (SI::ParentalRatingDescriptor *)d;
SI::ParentalRatingDescriptor::Rating Rating;
for (SI::Loop::Iterator it3; prd->ratingLoop.getNext(Rating, it3); ) {
if (I18nIsPreferredLanguage(Setup.EPGLanguages, Rating.languageCode, LanguagePreferenceRating)) {
int ParentalRating = (Rating.getRating() & 0xFF);
switch (ParentalRating) {
// values defined by the DVB standard (minimum age = rating + 3 years):
case 0x01 ... 0x0F: ParentalRating += 3; break;
// values defined by broadcaster CSAT (now why didn't they just use 0x07, 0x09 and 0x0D?):
case 0x11: ParentalRating = 10; break;
case 0x12: ParentalRating = 12; break;
case 0x13: ParentalRating = 16; break;
default: ParentalRating = 0;
}
pEvent->SetParentalRating(ParentalRating);
}
}
}
break;
case SI::PDCDescriptorTag: {
SI::PDCDescriptor *pd = (SI::PDCDescriptor *)d;
t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
int month = t.tm_mon;
t.tm_mon = pd->getMonth() - 1;
t.tm_mday = pd->getDay();
t.tm_hour = pd->getHour();
t.tm_min = pd->getMinute();
t.tm_sec = 0;
if (month == 11 && t.tm_mon == 0) // current month is dec, but event is in jan
t.tm_year++;
else if (month == 0 && t.tm_mon == 11) // current month is jan, but event is in dec
t.tm_year--;
time_t vps = mktime(&t);
pEvent->SetVps(vps);
}
break;
case SI::TimeShiftedEventDescriptorTag: {
SI::TimeShiftedEventDescriptor *tsed = (SI::TimeShiftedEventDescriptor *)d;
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, channel->Nid(), channel->Tid(), tsed->getReferenceServiceId()));
if (!rSchedule)
break;
rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
if (!rEvent)
break;
pEvent->SetTitle(rEvent->Title());
pEvent->SetShortText(rEvent->ShortText());
pEvent->SetDescription(rEvent->Description());
}
break;
case SI::LinkageDescriptorTag: {
SI::LinkageDescriptor *ld = (SI::LinkageDescriptor *)d;
tChannelID linkID(Source, ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
if (ld->getLinkageType() == 0xB0) { // Premiere World
bool hit = StartTime <= Now && Now < StartTime + Duration;
if (hit) {
char linkName[ld->privateData.getLength() + 1];
strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName));
// TODO is there a standard way to determine the character set of this string?
cChannel *link = Channels.GetByChannelID(linkID);
if (link != channel) { // only link to other channels, not the same one
//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
if (link) {
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
link->SetName(linkName, "", "");
}
else if (Setup.UpdateChannels >= 4) {
cChannel *transponder = channel;
if (channel->Tid() != ld->getTransportStreamId())
transponder = Channels.GetByTransponderID(linkID);
link = Channels.NewChannel(transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
//XXX patFilter->Trigger();
}
if (link) {
if (!LinkChannels)
LinkChannels = new cLinkChannels;
LinkChannels->Add(new cLinkChannel(link));
}
}
else
channel->SetPortalName(linkName);
}
}
}
break;
case SI::ComponentDescriptorTag: {
SI::ComponentDescriptor *cd = (SI::ComponentDescriptor *)d;
uchar Stream = cd->getStreamContent();
uchar Type = cd->getComponentType();
if (1 <= Stream && Stream <= 4 && Type != 0) { // 1=video, 2=audio, 3=subtitles, 4=AC3
if (!Components)
Components = new cComponents;
char buffer[Utf8BufSize(256)];
Components->SetComponent(Components->NumComponents(), Stream, Type, I18nNormalizeLanguageCode(cd->languageCode), cd->description.getText(buffer, sizeof(buffer)));
}
}
break;
default: ;
}
delete d;
}
if (!rEvent) {
if (ShortEventDescriptor) {
char buffer[Utf8BufSize(256)];
pEvent->SetTitle(ShortEventDescriptor->name.getText(buffer, sizeof(buffer)));
pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer, sizeof(buffer)));
}
else if (!HasExternalData) {
pEvent->SetTitle(NULL);
pEvent->SetShortText(NULL);
}
if (ExtendedEventDescriptors) {
char buffer[Utf8BufSize(ExtendedEventDescriptors->getMaximumTextLength(": ")) + 1];
pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer, sizeof(buffer), ": "));
}
else if (!HasExternalData)
pEvent->SetDescription(NULL);
}
delete ExtendedEventDescriptors;
delete ShortEventDescriptor;
pEvent->SetComponents(Components);
if (!HasExternalData)
pEvent->FixEpgBugs();
if (LinkChannels)
channel->SetLinkChannels(LinkChannels);
Modified = true;
}
if (Tid == 0x4E) {
if (Empty && getSectionNumber() == 0)
// ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running
pSchedule->ClrRunningStatus(channel);
pSchedule->SetPresentSeen();
}
if (OnlyRunningStatus)
return;
if (Modified) {
pSchedule->Sort();
if (!HasExternalData)
pSchedule->DropOutdated(SegmentStart, SegmentEnd, Tid, getVersionNumber());
Schedules->SetModified(pSchedule);
}
}
// --- cTDT ------------------------------------------------------------------
class cTDT : public SI::TDT {
private:
static cMutex mutex;
static int lastDiff;
public:
cTDT(const u_char *Data);
};
cMutex cTDT::mutex;
int cTDT::lastDiff = 0;
cTDT::cTDT(const u_char *Data)
:SI::TDT(Data, false)
{
CheckParse();
time_t sattim = getTime();
time_t loctim = time(NULL);
int diff = abs(sattim - loctim);
if (diff > 2) {
mutex.Lock();
if (abs(diff - lastDiff) < 3) {
if (stime(&sattim) == 0)
isyslog("system time changed from %s (%ld) to %s (%ld)", *TimeToString(loctim), loctim, *TimeToString(sattim), sattim);
else
esyslog("ERROR while setting system time: %m");
}
lastDiff = diff;
mutex.Unlock();
}
}
// --- cEitFilter ------------------------------------------------------------
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)
if (Setup.SetSystemTime && Setup.TimeTransponder)
Set(0x14, 0x70); // TDT
}
void cEitFilter::SetDisableUntil(time_t Time)
{
disableUntil = Time;
}
void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
{
if (disableUntil) {
if (time(NULL) > disableUntil)
disableUntil = 0;
else
return;
}
switch (Pid) {
case 0x12: {
if (Tid >= 0x4E && Tid <= 0x6F) {
cSchedulesLock SchedulesLock(true, 10);
cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
if (Schedules)
cEIT EIT(Schedules, Source(), Tid, Data);
else {
// If we don't get a write lock, let's at least get a read lock, so
// that we can set the running status and 'seen' timestamp (well, actually
// with a read lock we shouldn't be doing that, but it's only integers that
// get changed, so it should be ok)
cSchedulesLock SchedulesLock;
cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
if (Schedules)
cEIT EIT(Schedules, Source(), Tid, Data, true);
}
}
}
break;
case 0x14: {
if (Setup.SetSystemTime && Setup.TimeTransponder && ISTRANSPONDER(Transponder(), Setup.TimeTransponder))
cTDT TDT(Data);
}
break;
default: ;
}
}