mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
1345 lines
31 KiB
C
1345 lines
31 KiB
C
/***************************************************************************
|
|
eit.c - description
|
|
-------------------
|
|
begin : Fri Aug 25 2000
|
|
copyright : (C) 2000 by Robert Schneider
|
|
email : Robert.Schneider@web.de
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* $Id: eit.c 1.13 2001/03/03 13:23:00 kls Exp $
|
|
***************************************************************************/
|
|
|
|
#include "eit.h"
|
|
#include <ctype.h>
|
|
#include <dvb_comcode.h>
|
|
#include <dvb_v4l.h>
|
|
#include <fcntl.h>
|
|
#include <fstream.h>
|
|
#include <iomanip.h>
|
|
#include <iostream.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include "config.h"
|
|
#include "videodir.h"
|
|
|
|
// --- cMJD ------------------------------------------------------------------
|
|
|
|
class cMJD {
|
|
public:
|
|
cMJD();
|
|
cMJD(u_char date_hi, u_char date_lo);
|
|
cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese);
|
|
~cMJD();
|
|
/** */
|
|
void ConvertToTime();
|
|
/** */
|
|
bool SetSystemTime();
|
|
/** */
|
|
time_t GetTime_t();
|
|
protected: // Protected attributes
|
|
/** */
|
|
time_t mjdtime;
|
|
protected: // Protected attributes
|
|
/** */
|
|
u_char time_second;
|
|
protected: // Protected attributes
|
|
/** */
|
|
u_char time_minute;
|
|
protected: // Protected attributes
|
|
/** */
|
|
u_char time_hour;
|
|
protected: // Protected attributes
|
|
/** */
|
|
u_short mjd;
|
|
};
|
|
|
|
cMJD::cMJD()
|
|
{
|
|
}
|
|
|
|
cMJD::cMJD(u_char date_hi, u_char date_lo)
|
|
{
|
|
mjd = date_hi << 8 | date_lo;
|
|
time_hour = time_minute = time_second = 0;
|
|
ConvertToTime();
|
|
}
|
|
|
|
cMJD::cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese)
|
|
{
|
|
mjd = date_hi << 8 | date_lo;
|
|
time_hour = timehr;
|
|
time_minute = timemi;
|
|
time_second = timese;
|
|
ConvertToTime();
|
|
}
|
|
|
|
cMJD::~cMJD()
|
|
{
|
|
}
|
|
|
|
/** */
|
|
void cMJD::ConvertToTime()
|
|
{
|
|
struct tm t;
|
|
|
|
t.tm_sec = time_second;
|
|
t.tm_min = time_minute;
|
|
t.tm_hour = time_hour;
|
|
int k;
|
|
|
|
t.tm_year = (int) ((mjd - 15078.2) / 365.25);
|
|
t.tm_mon = (int) ((mjd - 14956.1 - (int)(t.tm_year * 365.25)) / 30.6001);
|
|
t.tm_mday = (int) (mjd - 14956 - (int)(t.tm_year * 365.25) - (int)(t.tm_mon * 30.6001));
|
|
k = (t.tm_mon == 14 || t.tm_mon == 15) ? 1 : 0;
|
|
t.tm_year = t.tm_year + k;
|
|
t.tm_mon = t.tm_mon - 1 - k * 12;
|
|
t.tm_mon--;
|
|
|
|
t.tm_isdst = -1;
|
|
t.tm_gmtoff = 0;
|
|
|
|
mjdtime = timegm(&t);
|
|
|
|
//isyslog(LOG_INFO, "Time parsed = %s\n", ctime(&mjdtime));
|
|
}
|
|
|
|
/** */
|
|
bool cMJD::SetSystemTime()
|
|
{
|
|
struct tm *ptm;
|
|
time_t loctim;
|
|
|
|
ptm = localtime(&mjdtime);
|
|
loctim = time(NULL);
|
|
|
|
if (abs(mjdtime - loctim) > 2)
|
|
{
|
|
isyslog(LOG_INFO, "System Time = %s (%ld)\n", ctime(&loctim), loctim);
|
|
isyslog(LOG_INFO, "Local Time = %s (%ld)\n", ctime(&mjdtime), mjdtime);
|
|
if (stime(&mjdtime) < 0)
|
|
esyslog(LOG_ERR, "ERROR while setting system time: %m");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
/** */
|
|
time_t cMJD::GetTime_t()
|
|
{
|
|
return mjdtime;
|
|
}
|
|
|
|
// --- cTDT ------------------------------------------------------------------
|
|
|
|
typedef struct {
|
|
u_char table_id : 8;
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
u_char section_syntax_indicator : 1;
|
|
u_char : 3;
|
|
u_char section_length_hi : 4;
|
|
#else
|
|
u_char section_length_hi : 4;
|
|
u_char : 3;
|
|
u_char section_syntax_indicator : 1;
|
|
#endif
|
|
|
|
u_char section_length_lo : 8;
|
|
|
|
|
|
u_char utc_date_hi : 8;
|
|
u_char utc_date_lo : 8;
|
|
u_char utc_hour : 4;
|
|
u_char utc_hour_ten : 4;
|
|
u_char utc_min : 4;
|
|
u_char utc_min_ten : 4;
|
|
u_char utc_sec : 4;
|
|
u_char utc_sec_ten : 4;
|
|
} tdt_t;
|
|
|
|
class cTDT {
|
|
public:
|
|
cTDT(tdt_t *ptdt);
|
|
~cTDT();
|
|
/** */
|
|
bool SetSystemTime();
|
|
protected: // Protected attributes
|
|
/** */
|
|
tdt_t tdt;
|
|
/** */
|
|
cMJD mjd; // kls 2001-03-02: made this a member instead of a pointer (it wasn't deleted in the destructor!)
|
|
};
|
|
|
|
cTDT::cTDT(tdt_t *ptdt)
|
|
:tdt(*ptdt)
|
|
,mjd(tdt.utc_date_hi, tdt.utc_date_lo, tdt.utc_hour_ten * 10 + tdt.utc_hour,
|
|
tdt.utc_min_ten * 10 + tdt.utc_min,
|
|
tdt.utc_sec_ten * 10 + tdt.utc_sec)
|
|
{
|
|
}
|
|
|
|
cTDT::~cTDT()
|
|
{
|
|
}
|
|
/** */
|
|
bool cTDT::SetSystemTime()
|
|
{
|
|
return mjd.SetSystemTime();
|
|
}
|
|
|
|
// --- cEventInfo ------------------------------------------------------------
|
|
|
|
cEventInfo::cEventInfo(unsigned short serviceid, unsigned short eventid)
|
|
{
|
|
pTitle = NULL;
|
|
pSubtitle = NULL;
|
|
pExtendedDescription = NULL;
|
|
bIsPresent = bIsFollowing = false;
|
|
lDuration = 0;
|
|
tTime = 0;
|
|
uEventID = eventid;
|
|
uServiceID = serviceid;
|
|
cExtendedDescriptorNumber = 0;
|
|
nChannelNumber = 0;
|
|
}
|
|
|
|
cEventInfo::~cEventInfo()
|
|
{
|
|
delete pTitle;
|
|
delete pSubtitle;
|
|
delete pExtendedDescription;
|
|
}
|
|
|
|
/** */
|
|
const char * cEventInfo::GetTitle() const
|
|
{
|
|
return pTitle;
|
|
}
|
|
/** */
|
|
const char * cEventInfo::GetSubtitle() const
|
|
{
|
|
return pSubtitle;
|
|
}
|
|
/** */
|
|
const char * cEventInfo::GetExtendedDescription() const
|
|
{
|
|
return pExtendedDescription;
|
|
}
|
|
/** */
|
|
bool cEventInfo::IsPresent() const
|
|
{
|
|
return bIsPresent;
|
|
}
|
|
/** */
|
|
void cEventInfo::SetPresent(bool pres)
|
|
{
|
|
bIsPresent = pres;
|
|
}
|
|
/** */
|
|
bool cEventInfo::IsFollowing() const
|
|
{
|
|
return bIsFollowing;
|
|
}
|
|
/** */
|
|
void cEventInfo::SetFollowing(bool foll)
|
|
{
|
|
bIsFollowing = foll;
|
|
}
|
|
/** */
|
|
const char * cEventInfo::GetDate() const
|
|
{
|
|
static char szDate[25];
|
|
|
|
strftime(szDate, sizeof(szDate), "%d.%m.%Y", localtime(&tTime));
|
|
|
|
return szDate;
|
|
}
|
|
/** */
|
|
const char * cEventInfo::GetTimeString() const
|
|
{
|
|
static char szTime[25];
|
|
|
|
strftime(szTime, sizeof(szTime), "%R", localtime(&tTime));
|
|
|
|
return szTime;
|
|
}
|
|
/** */
|
|
const char * cEventInfo::GetEndTimeString() const
|
|
{
|
|
static char szEndTime[25];
|
|
time_t tEndTime = tTime + lDuration;
|
|
|
|
strftime(szEndTime, sizeof(szEndTime), "%R", localtime(&tEndTime));
|
|
|
|
return szEndTime;
|
|
}
|
|
/** */
|
|
time_t cEventInfo::GetTime() const
|
|
{
|
|
return tTime;
|
|
}
|
|
/** */
|
|
long cEventInfo::GetDuration() const
|
|
{
|
|
return lDuration;
|
|
}
|
|
/** */
|
|
unsigned short cEventInfo::GetEventID() const
|
|
{
|
|
return uEventID;
|
|
}
|
|
/** */
|
|
bool cEventInfo::SetTitle(char *string)
|
|
{
|
|
if (string == NULL)
|
|
return false;
|
|
|
|
pTitle = strdup(string);
|
|
if (pTitle == NULL)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
/** */
|
|
bool cEventInfo::SetSubtitle(char *string)
|
|
{
|
|
if (string == NULL)
|
|
return false;
|
|
|
|
pSubtitle = strdup(string);
|
|
if (pSubtitle == NULL)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
/** */
|
|
bool cEventInfo::AddExtendedDescription(char *string)
|
|
{
|
|
int size = 0;
|
|
bool first = true;
|
|
char *p;
|
|
|
|
if (string == NULL)
|
|
return false;
|
|
|
|
if (pExtendedDescription)
|
|
{
|
|
first = false;
|
|
size += strlen(pExtendedDescription);
|
|
}
|
|
|
|
size += (strlen(string) + 1);
|
|
|
|
p = (char *)realloc(pExtendedDescription, size);
|
|
if (p == NULL)
|
|
return false;
|
|
|
|
if (first)
|
|
*p = 0;
|
|
|
|
strcat(p, string);
|
|
|
|
pExtendedDescription = p;
|
|
|
|
return true;
|
|
}
|
|
/** */
|
|
void cEventInfo::SetTime(time_t t)
|
|
{
|
|
tTime = t;
|
|
}
|
|
/** */
|
|
void cEventInfo::SetDuration(long l)
|
|
{
|
|
lDuration = l;
|
|
}
|
|
/** */
|
|
void cEventInfo::SetEventID(unsigned short evid)
|
|
{
|
|
uEventID = evid;
|
|
}
|
|
/** */
|
|
void cEventInfo::SetServiceID(unsigned short servid)
|
|
{
|
|
uServiceID = servid;
|
|
}
|
|
/** */
|
|
u_char cEventInfo::GetExtendedDescriptorNumber() const
|
|
{
|
|
return cExtendedDescriptorNumber;
|
|
}
|
|
/** */
|
|
void cEventInfo::IncreaseExtendedDescriptorNumber()
|
|
{
|
|
cExtendedDescriptorNumber++;
|
|
}
|
|
|
|
/** */
|
|
unsigned short cEventInfo::GetServiceID() const
|
|
{
|
|
return uServiceID;
|
|
}
|
|
|
|
/** */
|
|
void cEventInfo::Dump(FILE *f) const
|
|
{
|
|
if (tTime + lDuration >= time(NULL)) {
|
|
fprintf(f, "E %u %ld %ld\n", uEventID, tTime, lDuration);
|
|
if (!isempty(pTitle))
|
|
fprintf(f, "T %s\n", pTitle);
|
|
if (!isempty(pSubtitle))
|
|
fprintf(f, "S %s\n", pSubtitle);
|
|
if (!isempty(pExtendedDescription))
|
|
fprintf(f, "D %s\n", pExtendedDescription);
|
|
fprintf(f, "e\n");
|
|
}
|
|
}
|
|
|
|
// --- cSchedule -------------------------------------------------------------
|
|
|
|
cSchedule::cSchedule(unsigned short servid)
|
|
{
|
|
pPresent = pFollowing = NULL;
|
|
uServiceID = servid;
|
|
}
|
|
|
|
|
|
cSchedule::~cSchedule()
|
|
{
|
|
}
|
|
/** */
|
|
const cEventInfo * cSchedule::GetPresentEvent() const
|
|
{
|
|
// checking temporal sanity of present event (kls 2000-11-01)
|
|
time_t now = time(NULL);
|
|
if (pPresent && !(pPresent->GetTime() <= now && now <= pPresent->GetTime() + pPresent->GetDuration()))
|
|
{
|
|
cEventInfo *pe = Events.First();
|
|
while (pe != NULL)
|
|
{
|
|
if (pe->GetTime() <= now && now <= pe->GetTime() + pe->GetDuration())
|
|
return pe;
|
|
pe = Events.Next(pe);
|
|
}
|
|
}
|
|
return pPresent;
|
|
}
|
|
/** */
|
|
const cEventInfo * cSchedule::GetFollowingEvent() const
|
|
{
|
|
// checking temporal sanity of following event (kls 2000-11-01)
|
|
time_t now = time(NULL);
|
|
const cEventInfo *pr = GetPresentEvent(); // must have it verified!
|
|
if (pFollowing && !(pr && pr->GetTime() + pr->GetDuration() <= pFollowing->GetTime()))
|
|
{
|
|
int minDt = INT_MAX;
|
|
cEventInfo *pe = Events.First(), *pf = NULL;
|
|
while (pe != NULL)
|
|
{
|
|
int dt = pe->GetTime() - now;
|
|
if (dt > 0 && dt < minDt)
|
|
{
|
|
minDt = dt;
|
|
pf = pe;
|
|
}
|
|
pe = Events.Next(pe);
|
|
}
|
|
return pf;
|
|
}
|
|
return pFollowing;
|
|
}
|
|
/** */
|
|
void cSchedule::SetServiceID(unsigned short servid)
|
|
{
|
|
uServiceID = servid;
|
|
}
|
|
/** */
|
|
unsigned short cSchedule::GetServiceID() const
|
|
{
|
|
return uServiceID;
|
|
}
|
|
/** */
|
|
const cEventInfo * cSchedule::GetEvent(unsigned short uEventID) const
|
|
{
|
|
cEventInfo *pe = Events.First();
|
|
while (pe != NULL)
|
|
{
|
|
if (pe->GetEventID() == uEventID)
|
|
return pe;
|
|
|
|
pe = Events.Next(pe);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
/** */
|
|
const cEventInfo * cSchedule::GetEvent(time_t tTime) const
|
|
{
|
|
cEventInfo *pe = Events.First();
|
|
while (pe != NULL)
|
|
{
|
|
if (pe->GetTime() == tTime)
|
|
return pe;
|
|
|
|
pe = Events.Next(pe);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
/** */
|
|
bool cSchedule::SetPresentEvent(cEventInfo *pEvent)
|
|
{
|
|
if (pPresent != NULL)
|
|
pPresent->SetPresent(false);
|
|
pPresent = pEvent;
|
|
pPresent->SetPresent(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
/** */
|
|
bool cSchedule::SetFollowingEvent(cEventInfo *pEvent)
|
|
{
|
|
if (pFollowing != NULL)
|
|
pFollowing->SetFollowing(false);
|
|
pFollowing = pEvent;
|
|
pFollowing->SetFollowing(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
/** */
|
|
void cSchedule::Cleanup()
|
|
{
|
|
Cleanup(time(NULL));
|
|
}
|
|
|
|
/** */
|
|
void cSchedule::Cleanup(time_t tTime)
|
|
{
|
|
cEventInfo *pEvent;
|
|
for (int a = 0; true ; a++)
|
|
{
|
|
pEvent = Events.Get(a);
|
|
if (pEvent == NULL)
|
|
break;
|
|
if (pEvent->GetTime() + pEvent->GetDuration() < tTime)
|
|
{
|
|
Events.Del(pEvent);
|
|
a--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** */
|
|
void cSchedule::Dump(FILE *f) const
|
|
{
|
|
cChannel *channel = Channels.GetByServiceID(uServiceID);
|
|
if (channel)
|
|
{
|
|
fprintf(f, "C %u %s\n", uServiceID, channel->name);
|
|
for (cEventInfo *p = Events.First(); p; p = Events.Next(p))
|
|
p->Dump(f);
|
|
fprintf(f, "c\n");
|
|
}
|
|
}
|
|
|
|
// --- cSchedules ------------------------------------------------------------
|
|
|
|
cSchedules::cSchedules()
|
|
{
|
|
pCurrentSchedule = NULL;
|
|
uCurrentServiceID = 0;
|
|
}
|
|
|
|
cSchedules::~cSchedules()
|
|
{
|
|
}
|
|
/** */
|
|
bool cSchedules::SetCurrentServiceID(unsigned short servid)
|
|
{
|
|
pCurrentSchedule = GetSchedule(servid);
|
|
if (pCurrentSchedule == NULL)
|
|
{
|
|
Add(new cSchedule(servid));
|
|
pCurrentSchedule = GetSchedule(servid);
|
|
if (pCurrentSchedule == NULL)
|
|
return false;
|
|
}
|
|
|
|
uCurrentServiceID = servid;
|
|
|
|
return true;
|
|
}
|
|
/** */
|
|
const cSchedule * cSchedules::GetSchedule() const
|
|
{
|
|
return pCurrentSchedule;
|
|
}
|
|
/** */
|
|
const cSchedule * cSchedules::GetSchedule(unsigned short servid) const
|
|
{
|
|
cSchedule *p;
|
|
|
|
p = First();
|
|
while (p != NULL)
|
|
{
|
|
if (p->GetServiceID() == servid)
|
|
return p;
|
|
p = Next(p);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** */
|
|
void cSchedules::Cleanup()
|
|
{
|
|
cSchedule *p;
|
|
|
|
p = First();
|
|
while (p != NULL)
|
|
{
|
|
p->Cleanup(time(NULL));
|
|
p = Next(p);
|
|
}
|
|
}
|
|
|
|
/** */
|
|
void cSchedules::Dump(FILE *f) const
|
|
{
|
|
for (cSchedule *p = First(); p; p = Next(p))
|
|
p->Dump(f);
|
|
}
|
|
|
|
// --- cEIT ------------------------------------------------------------------
|
|
|
|
#define DEC(N) dec << setw(N) << setfill(int('0'))
|
|
#define HEX(N) hex << setw(N) << setfill(int('0'))
|
|
|
|
#define EIT_STUFFING_DESCRIPTOR 0x42
|
|
#define EIT_LINKAGE_DESCRIPTOR 0x4a
|
|
#define EIT_SHORT_EVENT_DESCRIPTOR 0x4d
|
|
#define EIT_EXTENDED_EVENT_DESCRIPTOR 0x4e
|
|
#define EIT_TIME_SHIFTED_EVENT_DESCRIPTOR 0x4f
|
|
#define EIT_COMPONENT_DESCRIPTOR 0x50
|
|
#define EIT_CA_IDENTIFIER_DESCRIPTOR 0x53
|
|
#define EIT_CONTENT_DESCRIPTOR 0x54
|
|
#define EIT_PARENTAL_RATING_DESCRIPTOR 0x55
|
|
#define EIT_TELEPHONE_DESCRIPTOR 0x57
|
|
#define EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR 0x5e
|
|
#define EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR 0x5f
|
|
#define EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR 0x61
|
|
#define EIT_DATA_BROADCAST_DESCRIPTOR 0x64
|
|
#define EIT_PDC_DESCRIPTOR 0x69
|
|
|
|
typedef struct eit_struct {
|
|
u_char table_id : 8;
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
u_char section_syntax_indicator : 1;
|
|
u_char : 3;
|
|
u_char section_length_hi : 4;
|
|
#else
|
|
u_char section_length_hi : 4;
|
|
u_char : 3;
|
|
u_char section_syntax_indicator : 1;
|
|
#endif
|
|
|
|
u_char section_length_lo : 8;
|
|
|
|
u_char service_id_hi : 8;
|
|
u_char service_id_lo : 8;
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
u_char : 2;
|
|
u_char version_number : 5;
|
|
u_char current_next_indicator : 1;
|
|
#else
|
|
u_char current_next_indicator : 1;
|
|
u_char version_number : 5;
|
|
u_char : 2;
|
|
#endif
|
|
|
|
u_char section_number : 8;
|
|
u_char last_section_number : 8;
|
|
u_char transport_stream_id_hi : 8;
|
|
u_char transport_stream_id_lo : 8;
|
|
u_char original_network_id_hi : 8;
|
|
u_char original_network_id_lo : 8;
|
|
u_char segment_last_section_number : 8;
|
|
u_char segment_last_table_id : 8;
|
|
} eit_t;
|
|
|
|
typedef struct eit_loop_struct {
|
|
u_char event_id_hi : 8;
|
|
u_char event_id_lo : 8;
|
|
|
|
u_char date_hi : 8;
|
|
u_char date_lo : 8;
|
|
u_char time_hour : 4;
|
|
u_char time_hour_ten : 4;
|
|
u_char time_minute : 4;
|
|
u_char time_minute_ten : 4;
|
|
u_char time_second : 4;
|
|
u_char time_second_ten : 4;
|
|
|
|
u_char dur_hour : 4;
|
|
u_char dur_hour_ten : 4;
|
|
u_char dur_minute : 4;
|
|
u_char dur_minute_ten : 4;
|
|
u_char dur_second : 4;
|
|
u_char dur_second_ten : 4;
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
u_char running_status : 3;
|
|
u_char free_ca_mode : 1;
|
|
u_char descriptors_loop_length_hi : 4;
|
|
#else
|
|
u_char descriptors_loop_length_hi : 4;
|
|
u_char free_ca_mode : 1;
|
|
u_char running_status : 3;
|
|
#endif
|
|
|
|
u_char descriptors_loop_length_lo : 8;
|
|
} eit_loop_t;
|
|
|
|
typedef struct eit_short_event_struct {
|
|
u_char descriptor_tag : 8;
|
|
u_char descriptor_length : 8;
|
|
|
|
u_char language_code_1 : 8;
|
|
u_char language_code_2 : 8;
|
|
u_char language_code_3 : 8;
|
|
|
|
u_char event_name_length : 8;
|
|
} eit_short_event_t;
|
|
|
|
typedef struct eit_extended_event_struct {
|
|
u_char descriptor_tag : 8;
|
|
u_char descriptor_length : 8;
|
|
|
|
u_char last_descriptor_number : 4;
|
|
u_char descriptor_number : 4;
|
|
|
|
u_char language_code_1 : 8;
|
|
u_char language_code_2 : 8;
|
|
u_char language_code_3 : 8;
|
|
|
|
u_char length_of_items : 8;
|
|
} eit_extended_event_t;
|
|
|
|
typedef struct eit_content_descriptor {
|
|
u_char descriptor_tag : 8;
|
|
u_char descriptor_length : 8;
|
|
} eit_content_descriptor_t;
|
|
|
|
typedef struct eit_content_loop {
|
|
u_char content_nibble_level_2 : 4;
|
|
u_char content_nibble_level_1 : 4;
|
|
u_char user_nibble_2 : 4;
|
|
u_char user_nibble_1 : 4;
|
|
} eit_content_loop_t;
|
|
|
|
class cEIT {
|
|
private:
|
|
cSchedules *schedules;
|
|
public:
|
|
cEIT(void *buf, int length, cSchedules *Schedules);
|
|
~cEIT();
|
|
/** */
|
|
int ProcessEIT();
|
|
|
|
protected: // Protected methods
|
|
/** */
|
|
int strdvbcpy(unsigned char *dst, unsigned char *src, int max);
|
|
/** returns true if this EIT covers a
|
|
present/following information, false if it's
|
|
schedule information */
|
|
bool IsPresentFollowing();
|
|
/** */
|
|
bool WriteShortEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf);
|
|
/** */
|
|
bool WriteExtEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf);
|
|
protected: // Protected attributes
|
|
int buflen;
|
|
protected: // Protected attributes
|
|
/** */
|
|
u_char buffer[4097];
|
|
/** Table ID of this EIT struct */
|
|
u_char tid;
|
|
/** EITs service id (program number) */
|
|
u_short pid;
|
|
};
|
|
|
|
cEIT::cEIT(void * buf, int length, cSchedules *Schedules)
|
|
{
|
|
buflen = length < int(sizeof(buffer)) ? length : sizeof(buffer);
|
|
memset(buffer, 0, sizeof(buffer));
|
|
memcpy(buffer, buf, buflen);
|
|
tid = buffer[0];
|
|
schedules = Schedules;
|
|
}
|
|
|
|
cEIT::~cEIT()
|
|
{
|
|
}
|
|
|
|
/** */
|
|
int cEIT::ProcessEIT()
|
|
{
|
|
int bufact = 0;
|
|
eit_t *eit;
|
|
eit_loop_t *eitloop;
|
|
u_char tmp[256];
|
|
|
|
if (bufact + (int)sizeof(eit_t) > buflen)
|
|
return 0;
|
|
eit = (eit_t *)buffer;
|
|
bufact += sizeof(eit_t);
|
|
|
|
unsigned int service = (eit->service_id_hi << 8) | eit->service_id_lo;
|
|
|
|
while(bufact + (int)sizeof(eit_loop_t) <= buflen)
|
|
{
|
|
eitloop = (eit_loop_t *)&buffer[bufact];
|
|
bufact += sizeof(eit_loop_t);
|
|
|
|
int descdatalen = (eitloop->descriptors_loop_length_hi << 8) + eitloop->descriptors_loop_length_lo;
|
|
int descdataact = 0;
|
|
|
|
while (descdataact < descdatalen && bufact < buflen)
|
|
{
|
|
switch (buffer[bufact])
|
|
{
|
|
eit_content_descriptor_t *cont;
|
|
eit_content_loop_t *contloop;
|
|
|
|
case EIT_STUFFING_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_STUFFING_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_LINKAGE_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_LINKAGE_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_SHORT_EVENT_DESCRIPTOR:
|
|
WriteShortEventDescriptor(service, eitloop, &buffer[bufact]);
|
|
break;
|
|
|
|
case EIT_EXTENDED_EVENT_DESCRIPTOR:
|
|
WriteExtEventDescriptor(service, eitloop, &buffer[bufact]);
|
|
break;
|
|
|
|
case EIT_TIME_SHIFTED_EVENT_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_TIME_SHIFTED_EVENT_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_COMPONENT_DESCRIPTOR :
|
|
if (buffer[bufact + 1] > 6) // kls 2001-02-24: otherwise strncpy() causes a segfault in strdvbcpy()
|
|
strdvbcpy(tmp, &buffer[bufact + 8], buffer[bufact + 1] - 6);
|
|
//dsyslog(LOG_INFO, "Found EIT_COMPONENT_DESCRIPTOR %c%c%c 0x%02x/0x%02x/0x%02x '%s'\n", buffer[bufact + 5], buffer[bufact + 6], buffer[bufact + 7], buffer[2], buffer[3], buffer[4], tmp);
|
|
break;
|
|
|
|
case EIT_CA_IDENTIFIER_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_CA_IDENTIFIER_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_CONTENT_DESCRIPTOR :
|
|
cont = (eit_content_descriptor_t *)buffer;
|
|
contloop = (eit_content_loop_t *)&buffer[sizeof(eit_content_descriptor_t)];
|
|
//dsyslog(LOG_INFO, "Found EIT_CONTENT_DESCRIPTOR 0x%02x/0x%02x\n", contloop->content_nibble_level_1, contloop->content_nibble_level_2);
|
|
break;
|
|
|
|
case EIT_PARENTAL_RATING_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_PARENTAL_RATING_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_TELEPHONE_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_TELEPHONE_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_DATA_BROADCAST_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_DATA_BROADCAST_DESCRIPTOR");
|
|
break;
|
|
|
|
case EIT_PDC_DESCRIPTOR :
|
|
//dsyslog(LOG_INFO, "Found EIT_PDC_DESCRIPTOR");
|
|
break;
|
|
|
|
default:
|
|
//dsyslog(LOG_INFO, "Found unhandled descriptor 0x%02x with length of %04d\n", (int)buffer[bufact], (int)buffer[bufact + 1]);
|
|
break;
|
|
}
|
|
descdataact += (buffer[bufact + 1] + 2);
|
|
bufact += (buffer[bufact + 1] + 2);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** */
|
|
int cEIT::strdvbcpy(unsigned char *dst, unsigned char *src, int max)
|
|
{
|
|
int a = 0;
|
|
|
|
// kls 2001-02-24: if we come in with negative values, the caller must
|
|
// have done something wrong and the strncpy() below will cause a segfault
|
|
if (max <= 0)
|
|
{
|
|
*dst = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (*src == 0x05 || (*src >= 0x20 && *src <= 0xff))
|
|
{
|
|
for (a = 0; a < max; a++)
|
|
{
|
|
if (*src == 0)
|
|
break;
|
|
|
|
if ((*src >= ' ' && *src <= '~') || (*src >= 0xa0 && *src <= 0xff))
|
|
*dst++ = *src++;
|
|
else
|
|
{
|
|
// if ((*src > '~' && *src < 0xa0) || *src == 0xff)
|
|
// cerr << "found special character 0x" << HEX(2) << (int)*src << endl;
|
|
src++;
|
|
}
|
|
}
|
|
*dst = 0;
|
|
}
|
|
else
|
|
{
|
|
const char *ret;
|
|
|
|
switch (*src)
|
|
{
|
|
case 0x01: ret = "Coding according to character table 1"; break;
|
|
case 0x02: ret = "Coding according to character table 2"; break;
|
|
case 0x03: ret = "Coding according to character table 3"; break;
|
|
case 0x04: ret = "Coding according to character table 4"; break;
|
|
case 0x10: ret = "Coding according to ISO/IEC 8859"; break;
|
|
case 0x11: ret = "Coding according to ISO/IEC 10646"; break;
|
|
case 0x12: ret = "Coding according to KSC 5601"; break;
|
|
default: ret = "Unknown coding"; break;
|
|
}
|
|
strncpy((char *)dst, ret, max);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/** returns true if this EIT covers a
|
|
present/following information, false if it's
|
|
schedule information */
|
|
bool cEIT::IsPresentFollowing()
|
|
{
|
|
if (tid == 0x4e || tid == 0x4f)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/** */
|
|
bool cEIT::WriteShortEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf)
|
|
{
|
|
u_char tmp[256];
|
|
eit_short_event_t *evt = (eit_short_event_t *)buf;
|
|
unsigned short eventid = (unsigned short)((eitloop->event_id_hi << 8) | eitloop->event_id_lo);
|
|
cEventInfo *pEvent;
|
|
|
|
//isyslog(LOG_INFO, "Found Short Event Descriptor");
|
|
|
|
cSchedule *pSchedule = (cSchedule *)schedules->GetSchedule(service);
|
|
if (pSchedule == NULL)
|
|
{
|
|
schedules->Add(new cSchedule(service));
|
|
pSchedule = (cSchedule *)schedules->GetSchedule(service);
|
|
if (pSchedule == NULL)
|
|
return false;
|
|
}
|
|
|
|
/* cSchedule::GetPresentEvent() and cSchedule::GetFollowingEvent() verify
|
|
the temporal sanity of these events, so calling them here appears to
|
|
be a bad idea... (kls 2000-11-01)
|
|
//
|
|
// if we are working on a present/following info, let's see whether
|
|
// we already have present/following info for this service and if yes
|
|
// check whether it's the same eventid, if yes, just return, nothing
|
|
// left to do.
|
|
//
|
|
if (IsPresentFollowing())
|
|
{
|
|
if (eitloop->running_status == 4 || eitloop->running_status == 3)
|
|
pEvent = (cEventInfo *)pSchedule->GetPresentEvent();
|
|
else
|
|
pEvent = (cEventInfo *)pSchedule->GetFollowingEvent();
|
|
|
|
if (pEvent != NULL)
|
|
if (pEvent->GetEventID() == eventid)
|
|
return true;
|
|
}
|
|
*/
|
|
|
|
//
|
|
// let's see whether we have that eventid already
|
|
// in case not, we have to create a new cEventInfo for it
|
|
//
|
|
pEvent = (cEventInfo *)pSchedule->GetEvent(eventid);
|
|
if (pEvent == NULL)
|
|
{
|
|
pSchedule->Events.Add(new cEventInfo(service, eventid));
|
|
pEvent = (cEventInfo *)pSchedule->GetEvent(eventid);
|
|
if (pEvent == NULL)
|
|
return false;
|
|
|
|
strdvbcpy(tmp, &buf[sizeof(eit_short_event_t)], evt->event_name_length);
|
|
pEvent->SetTitle((char *)tmp);
|
|
strdvbcpy(tmp, &buf[sizeof(eit_short_event_t) + evt->event_name_length + 1],
|
|
(int)buf[sizeof(eit_short_event_t) + evt->event_name_length]);
|
|
pEvent->SetSubtitle((char *)tmp);
|
|
cMJD mjd(eitloop->date_hi, eitloop->date_lo,
|
|
eitloop->time_hour_ten * 10 + eitloop->time_hour,
|
|
eitloop->time_minute_ten * 10 + eitloop->time_minute,
|
|
eitloop->time_second_ten * 10 + eitloop->time_second);
|
|
pEvent->SetTime(mjd.GetTime_t());
|
|
pEvent->SetDuration((long)((long)((eitloop->dur_hour_ten * 10 + eitloop->dur_hour) * 60l * 60l) +
|
|
(long)((eitloop->dur_minute_ten * 10 + eitloop->dur_minute) * 60l) +
|
|
(long)(eitloop->dur_second_ten * 10 + eitloop->dur_second)));
|
|
}
|
|
|
|
if (IsPresentFollowing())
|
|
{
|
|
if (eitloop->running_status == 4 || eitloop->running_status == 3)
|
|
pSchedule->SetPresentEvent(pEvent);
|
|
else if (eitloop->running_status == 1 || eitloop->running_status == 2 || eitloop->running_status == 0)
|
|
pSchedule->SetFollowingEvent(pEvent);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** */
|
|
bool cEIT::WriteExtEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf)
|
|
{
|
|
u_char tmp[256];
|
|
eit_extended_event_t *evt = (eit_extended_event_t *)buf;
|
|
int bufact, buflen;
|
|
unsigned short eventid = (unsigned short)((eitloop->event_id_hi << 8) | eitloop->event_id_lo);
|
|
cEventInfo *pEvent;
|
|
|
|
//isyslog(LOG_INFO, "Found Extended Event Descriptor");
|
|
|
|
cSchedule *pSchedule = (cSchedule *)schedules->GetSchedule(service);
|
|
if (pSchedule == NULL)
|
|
{
|
|
schedules->Add(new cSchedule(service));
|
|
pSchedule = (cSchedule *)schedules->GetSchedule(service);
|
|
if (pSchedule == NULL)
|
|
return false;
|
|
}
|
|
|
|
pEvent = (cEventInfo *)pSchedule->GetEvent(eventid);
|
|
if (pEvent == NULL)
|
|
return false;
|
|
|
|
if (evt->descriptor_number != pEvent->GetExtendedDescriptorNumber())
|
|
return false;
|
|
|
|
bufact = sizeof(eit_extended_event_t);
|
|
buflen = buf[1] + 2;
|
|
|
|
if (evt->length_of_items > 0)
|
|
{
|
|
while (bufact - sizeof(eit_extended_event_t) < evt->length_of_items)
|
|
{
|
|
strdvbcpy(tmp, &buf[bufact + 1], (int)buf[bufact]);
|
|
// could use value in tmp now to do something,
|
|
// haven't seen any items as of yet transmitted from satellite
|
|
bufact += (buf[bufact] + 1);
|
|
}
|
|
}
|
|
|
|
strdvbcpy(tmp, &buf[bufact + 1], (int)buf[bufact]);
|
|
if (pEvent->AddExtendedDescription((char *)tmp))
|
|
{
|
|
pEvent->IncreaseExtendedDescriptorNumber();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// --- cSIProcessor ----------------------------------------------------------
|
|
|
|
#define MAX_FILTERS 20
|
|
|
|
int cSIProcessor::numSIProcessors = 0;
|
|
cSchedules *cSIProcessor::schedules = NULL;
|
|
cMutex cSIProcessor::schedulesMutex;
|
|
|
|
/** */
|
|
cSIProcessor::cSIProcessor(const char *FileName)
|
|
{
|
|
masterSIProcessor = numSIProcessors == 0; // the first one becomes the 'master'
|
|
useTStime = false;
|
|
filters = NULL;
|
|
if ((fsvbi = open(FileName, O_RDONLY)) >= 0)
|
|
{
|
|
if (!numSIProcessors++) // the first one creates it
|
|
schedules = new cSchedules;
|
|
filters = (SIP_FILTER *)calloc(MAX_FILTERS, sizeof(SIP_FILTER));
|
|
}
|
|
else
|
|
LOG_ERROR_STR(FileName);
|
|
}
|
|
|
|
cSIProcessor::~cSIProcessor()
|
|
{
|
|
if (fsvbi >= 0)
|
|
{
|
|
Cancel();
|
|
ShutDownFilters();
|
|
delete filters;
|
|
if (!--numSIProcessors) // the last one deletes it
|
|
delete schedules;
|
|
close(fsvbi);
|
|
}
|
|
}
|
|
|
|
/** use the vbi device to parse all relevant SI
|
|
information and let the classes corresponding
|
|
to the tables write their information to the disk */
|
|
void cSIProcessor::Action()
|
|
{
|
|
if (fsvbi < 0) {
|
|
esyslog(LOG_ERR, "cSIProcessor::Action() called without open file - returning");
|
|
return;
|
|
}
|
|
|
|
dsyslog(LOG_INFO, "EIT processing thread started (pid=%d)%s", getpid(), masterSIProcessor ? " - master" : "");
|
|
|
|
unsigned char buf[4096+1]; // max. allowed size for any EIT section (+1 for safety ;-)
|
|
unsigned int seclen;
|
|
unsigned int pid;
|
|
time_t lastCleanup = time(NULL);
|
|
time_t lastDump = time(NULL);
|
|
struct pollfd pfd;
|
|
|
|
while(true)
|
|
{
|
|
if (masterSIProcessor)
|
|
{
|
|
time_t now = time(NULL);
|
|
struct tm *ptm = localtime(&now);
|
|
if (now - lastCleanup > 3600 && ptm->tm_hour == 5)
|
|
{
|
|
LOCK_THREAD;
|
|
|
|
schedulesMutex.Lock();
|
|
isyslog(LOG_INFO, "cleaning up schedules data");
|
|
schedules->Cleanup();
|
|
schedulesMutex.Unlock();
|
|
lastCleanup = now;
|
|
}
|
|
if (now - lastDump > 600)
|
|
{
|
|
LOCK_THREAD;
|
|
|
|
schedulesMutex.Lock();
|
|
FILE *f = fopen(AddDirectory(VideoDirectory, "epg.data"), "w");
|
|
if (f) {
|
|
schedules->Dump(f);
|
|
fclose(f);
|
|
}
|
|
lastDump = now;
|
|
schedulesMutex.Unlock();
|
|
}
|
|
}
|
|
|
|
/* wait data become ready from the bitfilter */
|
|
pfd.fd = fsvbi;
|
|
pfd.events = POLLIN;
|
|
if(poll(&pfd, 1, 1000) != 0) /* timeout is 5 secs */
|
|
{
|
|
// fprintf(stderr, "<data>\n");
|
|
/* read section */
|
|
read(fsvbi, buf, 8);
|
|
seclen = (buf[6] << 8) | buf[7];
|
|
pid = (buf[4] << 8) | buf[5];
|
|
read(fsvbi, buf, seclen);
|
|
|
|
//dsyslog(LOG_INFO, "Received pid 0x%02x with table ID 0x%02x and length of %04d\n", pid, buf[0], seclen);
|
|
|
|
switch (pid)
|
|
{
|
|
case 0x14:
|
|
if (buf[0] == 0x70)
|
|
{
|
|
if (useTStime)
|
|
{
|
|
cTDT ctdt((tdt_t *)buf);
|
|
ctdt.SetSystemTime();
|
|
}
|
|
}
|
|
/*XXX this comes pretty often:
|
|
else
|
|
dsyslog(LOG_INFO, "Time packet was not 0x70 but 0x%02x\n", (int)buf[0]);
|
|
XXX*/
|
|
break;
|
|
|
|
case 0x12:
|
|
if (buf[0] != 0x72)
|
|
{
|
|
LOCK_THREAD;
|
|
|
|
schedulesMutex.Lock();
|
|
cEIT ceit(buf, seclen, schedules);
|
|
ceit.ProcessEIT();
|
|
schedulesMutex.Unlock();
|
|
}
|
|
else
|
|
dsyslog(LOG_INFO, "Received stuffing section in EIT\n");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOCK_THREAD;
|
|
|
|
//XXX this comes pretty often
|
|
//isyslog(LOG_INFO, "Received timeout from poll, refreshing filters\n");
|
|
RefreshFilters();
|
|
}
|
|
// WakeUp();
|
|
}
|
|
}
|
|
|
|
/** Add a filter with packet identifier pid and
|
|
table identifer tid */
|
|
bool cSIProcessor::AddFilter(u_char pid, u_char tid)
|
|
{
|
|
if (fsvbi < 0)
|
|
return false;
|
|
|
|
int section = ((int)tid << 8) | 0x00ff;
|
|
|
|
struct bitfilter filt = {
|
|
pid,
|
|
{ section, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
|
|
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
|
|
SECTION_CONTINUOS, 0,
|
|
FILTER_MEM,
|
|
{},
|
|
};
|
|
|
|
if (ioctl(fsvbi, VIDIOCSBITFILTER, &filt) < 0)
|
|
return false;
|
|
|
|
for (int a = 0; a < MAX_FILTERS; a++)
|
|
{
|
|
if (filters[a].inuse == false)
|
|
{
|
|
filters[a].pid = pid;
|
|
filters[a].tid = tid;
|
|
filters[a].handle = filt.handle;
|
|
filters[a].inuse = true;
|
|
// dsyslog(LOG_INFO, " Registered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** set whether local systems time should be
|
|
set by the received TDT or TOT packets */
|
|
bool cSIProcessor::SetUseTSTime(bool use)
|
|
{
|
|
useTStime = use;
|
|
return useTStime;
|
|
}
|
|
|
|
/** */
|
|
bool cSIProcessor::ShutDownFilters()
|
|
{
|
|
if (fsvbi < 0)
|
|
return false;
|
|
|
|
bool ret = true;
|
|
|
|
for (int a = 0; a < MAX_FILTERS; a++)
|
|
{
|
|
if (filters[a].inuse == true)
|
|
{
|
|
if (ioctl(fsvbi, VIDIOCSSHUTDOWNFILTER, &filters[a].handle) < 0)
|
|
ret = false;
|
|
|
|
// dsyslog(LOG_INFO, "Deregistered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
|
|
|
|
filters[a].inuse = false;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** */
|
|
bool cSIProcessor::SetCurrentServiceID(unsigned short servid)
|
|
{
|
|
LOCK_THREAD;
|
|
return schedules ? schedules->SetCurrentServiceID(servid) : false;
|
|
}
|
|
|
|
/** */
|
|
bool cSIProcessor::RefreshFilters()
|
|
{
|
|
if (fsvbi < 0)
|
|
return false;
|
|
|
|
bool ret = true;
|
|
|
|
ret = ShutDownFilters();
|
|
|
|
for (int a = 0; a < MAX_FILTERS; a++)
|
|
{
|
|
if (filters[a].inuse == false && filters[a].pid != 0 && filters[a].tid != 0)
|
|
{
|
|
if (!AddFilter(filters[a].pid, filters[a].tid))
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|