mirror of
https://github.com/rofafor/vdr-plugin-iptv.git
synced 2023-10-10 13:37:03 +02:00
482 lines
15 KiB
C
482 lines
15 KiB
C
/*
|
|
* device.c: IPTV plugin for the Video Disk Recorder
|
|
*
|
|
* See the README file for copyright information and how to reach the author.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "source.h"
|
|
#include "device.h"
|
|
|
|
#define IPTV_MAX_DEVICES MAXDEVICES
|
|
|
|
static cIptvDevice * IptvDevicesS[IPTV_MAX_DEVICES] = { NULL };
|
|
|
|
cIptvDevice::cIptvDevice(unsigned int indexP)
|
|
: deviceIndexM(indexP),
|
|
dvrFdM(-1),
|
|
isPacketDeliveredM(false),
|
|
isOpenDvrM(false),
|
|
sidScanEnabledM(false),
|
|
pidScanEnabledM(false),
|
|
channelIdM(tChannelID::InvalidID)
|
|
{
|
|
unsigned int bufsize = (unsigned int)MEGABYTE(IptvConfig.GetTsBufferSize());
|
|
bufsize -= (bufsize % TS_SIZE);
|
|
isyslog("creating IPTV device %d (CardIndex=%d)", deviceIndexM, CardIndex());
|
|
tsBufferM = new cRingBufferLinear(bufsize + 1, TS_SIZE, false,
|
|
*cString::sprintf("IPTV %d", deviceIndexM));
|
|
tsBufferM->SetTimeouts(10, 10);
|
|
ResetBuffering();
|
|
pUdpProtocolM = new cIptvProtocolUdp();
|
|
pCurlProtocolM = new cIptvProtocolCurl();
|
|
pHttpProtocolM = new cIptvProtocolHttp();
|
|
pFileProtocolM = new cIptvProtocolFile();
|
|
pExtProtocolM = new cIptvProtocolExt();
|
|
pIptvStreamerM = new cIptvStreamer(tsBufferM, (100 * TS_SIZE));
|
|
pPidScannerM = new cPidScanner();
|
|
// Initialize filter pointers
|
|
memset(secFiltersM, 0, sizeof(secFiltersM));
|
|
// Start section handler for iptv device
|
|
StartSectionHandler();
|
|
// Sid scanner must be created after the section handler
|
|
AttachFilter(pSidScannerM = new cSidScanner());
|
|
// Check if dvr fifo exists
|
|
struct stat sb;
|
|
cString filename = cString::sprintf(IPTV_DVR_FILENAME, deviceIndexM);
|
|
stat(filename, &sb);
|
|
if (S_ISFIFO(sb.st_mode)) {
|
|
dvrFdM = open(filename, O_RDWR | O_NONBLOCK);
|
|
if (dvrFdM >= 0)
|
|
dsyslog("IPTV device %d redirecting input stream to '%s'", deviceIndexM, *filename);
|
|
}
|
|
}
|
|
|
|
cIptvDevice::~cIptvDevice()
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
DELETE_POINTER(pIptvStreamerM);
|
|
DELETE_POINTER(pUdpProtocolM);
|
|
DELETE_POINTER(pCurlProtocolM);
|
|
DELETE_POINTER(pHttpProtocolM);
|
|
DELETE_POINTER(pFileProtocolM);
|
|
DELETE_POINTER(pExtProtocolM);
|
|
DELETE_POINTER(tsBufferM);
|
|
DELETE_POINTER(pPidScannerM);
|
|
DELETE_POINTER(pSidScannerM);
|
|
// Stop section handler of iptv device
|
|
StopSectionHandler();
|
|
// Destroy all filters
|
|
cMutexLock MutexLock(&mutexM);
|
|
for (int i = 0; i < eMaxSecFilterCount; ++i)
|
|
DeleteFilter(i);
|
|
// Close dvr fifo
|
|
if (dvrFdM >= 0) {
|
|
int fd = dvrFdM;
|
|
dvrFdM = -1;
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
bool cIptvDevice::Initialize(unsigned int deviceCountP)
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceCountP);
|
|
new cIptvSourceParam(IPTV_SOURCE_CHARACTER, "IPTV");
|
|
if (deviceCountP > IPTV_MAX_DEVICES)
|
|
deviceCountP = IPTV_MAX_DEVICES;
|
|
for (unsigned int i = 0; i < deviceCountP; ++i)
|
|
IptvDevicesS[i] = new cIptvDevice(i);
|
|
for (unsigned int i = deviceCountP; i < IPTV_MAX_DEVICES; ++i)
|
|
IptvDevicesS[i] = NULL;
|
|
return true;
|
|
}
|
|
|
|
unsigned int cIptvDevice::Count(void)
|
|
{
|
|
unsigned int count = 0;
|
|
debug("cIptvDevice::%s()", __FUNCTION__);
|
|
for (unsigned int i = 0; i < IPTV_MAX_DEVICES; ++i) {
|
|
if (IptvDevicesS[i] != NULL)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
cIptvDevice *cIptvDevice::GetIptvDevice(int cardIndexP)
|
|
{
|
|
//debug("cIptvDevice::%s(%d)", __FUNCTION__, cardIndexP);
|
|
for (unsigned int i = 0; i < IPTV_MAX_DEVICES; ++i) {
|
|
if ((IptvDevicesS[i] != NULL) && (IptvDevicesS[i]->CardIndex() == cardIndexP)) {
|
|
//debug("cIptvDevice::%s(%d): found!", __FUNCTION__, cardIndexP);
|
|
return IptvDevicesS[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
cString cIptvDevice::GetGeneralInformation(void)
|
|
{
|
|
//debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return cString::sprintf("IPTV device: %d\nCardIndex: %d\nStream: %s\nStream bitrate: %s\n%sChannel: %s",
|
|
deviceIndexM, CardIndex(),
|
|
pIptvStreamerM ? *pIptvStreamerM->GetInformation() : "",
|
|
pIptvStreamerM ? *pIptvStreamerM->GetStreamerStatistic() : "",
|
|
*GetBufferStatistic(),
|
|
*Channels.GetByNumber(cDevice::CurrentChannel())->ToText());
|
|
}
|
|
|
|
cString cIptvDevice::GetPidsInformation(void)
|
|
{
|
|
//debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return GetPidStatistic();
|
|
}
|
|
|
|
cString cIptvDevice::GetFiltersInformation(void)
|
|
{
|
|
//debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
unsigned int count = 0;
|
|
cString s("Active section filters:\n");
|
|
// loop through active section filters
|
|
cMutexLock MutexLock(&mutexM);
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (secFiltersM[i]) {
|
|
s = cString::sprintf("%sFilter %d: %s Pid=0x%02X (%s)\n", *s, i,
|
|
*secFiltersM[i]->GetSectionStatistic(), secFiltersM[i]->GetPid(),
|
|
id_pid(secFiltersM[i]->GetPid()));
|
|
if (++count > IPTV_STATS_ACTIVE_FILTERS_COUNT)
|
|
break;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
cString cIptvDevice::GetInformation(unsigned int pageP)
|
|
{
|
|
// generate information string
|
|
cString s;
|
|
switch (pageP) {
|
|
case IPTV_DEVICE_INFO_GENERAL:
|
|
s = GetGeneralInformation();
|
|
break;
|
|
case IPTV_DEVICE_INFO_PIDS:
|
|
s = GetPidsInformation();
|
|
break;
|
|
case IPTV_DEVICE_INFO_FILTERS:
|
|
s = GetFiltersInformation();
|
|
break;
|
|
case IPTV_DEVICE_INFO_PROTOCOL:
|
|
s = pIptvStreamerM ? *pIptvStreamerM->GetInformation() : "";
|
|
break;
|
|
case IPTV_DEVICE_INFO_BITRATE:
|
|
s = pIptvStreamerM ? *pIptvStreamerM->GetStreamerStatistic() : "";
|
|
break;
|
|
default:
|
|
s = cString::sprintf("%s%s%s",
|
|
*GetGeneralInformation(),
|
|
*GetPidsInformation(),
|
|
*GetFiltersInformation());
|
|
break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
cString cIptvDevice::DeviceType(void) const
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return "IPTV";
|
|
}
|
|
|
|
cString cIptvDevice::DeviceName(void) const
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return cString::sprintf("IPTV %d", deviceIndexM);
|
|
}
|
|
|
|
int cIptvDevice::SignalStrength(void) const
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return (100);
|
|
}
|
|
|
|
int cIptvDevice::SignalQuality(void) const
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return (100);
|
|
}
|
|
|
|
bool cIptvDevice::ProvidesSource(int sourceP) const
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return (cSource::IsType(sourceP, IPTV_SOURCE_CHARACTER));
|
|
}
|
|
|
|
bool cIptvDevice::ProvidesTransponder(const cChannel *channelP) const
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return (ProvidesSource(channelP->Source()));
|
|
}
|
|
|
|
bool cIptvDevice::ProvidesChannel(const cChannel *channelP, int priorityP, bool *needsDetachReceiversP) const
|
|
{
|
|
bool result = false;
|
|
bool hasPriority = (priorityP == IDLEPRIORITY) || (priorityP > this->Priority());
|
|
bool needsDetachReceivers = false;
|
|
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
|
|
if (channelP && ProvidesTransponder(channelP)) {
|
|
result = hasPriority;
|
|
if (Receiving()) {
|
|
if (channelP->GetChannelID() == channelIdM)
|
|
result = true;
|
|
else
|
|
needsDetachReceivers = Receiving();
|
|
}
|
|
}
|
|
if (needsDetachReceiversP)
|
|
*needsDetachReceiversP = needsDetachReceivers;
|
|
return result;
|
|
}
|
|
|
|
bool cIptvDevice::ProvidesEIT(void) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int cIptvDevice::NumProvidedSystems(void) const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
bool cIptvDevice::SetChannelDevice(const cChannel *channelP, bool liveViewP)
|
|
{
|
|
cIptvProtocolIf *protocol;
|
|
cIptvTransponderParameters itp(channelP->Parameters());
|
|
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
|
|
if (isempty(itp.Address())) {
|
|
error("Unrecognized IPTV address: %s", channelP->Parameters());
|
|
return false;
|
|
}
|
|
switch (itp.Protocol()) {
|
|
case cIptvTransponderParameters::eProtocolUDP:
|
|
protocol = pUdpProtocolM;
|
|
break;
|
|
case cIptvTransponderParameters::eProtocolCURL:
|
|
protocol = pCurlProtocolM;
|
|
break;
|
|
case cIptvTransponderParameters::eProtocolHTTP:
|
|
protocol = pHttpProtocolM;
|
|
break;
|
|
case cIptvTransponderParameters::eProtocolFILE:
|
|
protocol = pFileProtocolM;
|
|
break;
|
|
case cIptvTransponderParameters::eProtocolEXT:
|
|
protocol = pExtProtocolM;
|
|
break;
|
|
default:
|
|
error("Unrecognized IPTV protocol: %s", channelP->Parameters());
|
|
return false;
|
|
break;
|
|
}
|
|
sidScanEnabledM = itp.SidScan() ? true : false;
|
|
pidScanEnabledM = itp.PidScan() ? true : false;
|
|
if (pIptvStreamerM->Set(itp.Address(), itp.Parameter(), deviceIndexM, protocol)) {
|
|
channelIdM = channelP->GetChannelID();
|
|
if (sidScanEnabledM && pSidScannerM && IptvConfig.GetSectionFiltering())
|
|
pSidScannerM->SetChannel(channelIdM);
|
|
if (pidScanEnabledM && pPidScannerM)
|
|
pPidScannerM->SetChannel(channelIdM);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool cIptvDevice::SetPid(cPidHandle *handleP, int typeP, bool onP)
|
|
{
|
|
debug("cIptvDevice::%s(%d): pid=%d type=%d on=%d", __FUNCTION__, deviceIndexM, handleP->pid, typeP, onP);
|
|
return true;
|
|
}
|
|
|
|
bool cIptvDevice::DeleteFilter(unsigned int indexP)
|
|
{
|
|
if ((indexP < eMaxSecFilterCount) && secFiltersM[indexP]) {
|
|
//debug("cIptvDevice::%s(%d): index=%d", __FUNCTION__, deviceIndexM, indexP);
|
|
cIptvSectionFilter *tmp = secFiltersM[indexP];
|
|
secFiltersM[indexP] = NULL;
|
|
delete tmp;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cIptvDevice::IsBlackListed(u_short pidP, u_char tidP, u_char maskP) const
|
|
{
|
|
//debug("cIptvDevice::%s(%d): pid=%d tid=%02X mask=%02X", __FUNCTION__, deviceIndexM, pidP, tidP, maskP);
|
|
// loop through section filter table
|
|
for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) {
|
|
int index = IptvConfig.GetDisabledFilters(i);
|
|
// Check if matches
|
|
if ((index >= 0) && (index < SECTION_FILTER_TABLE_SIZE) &&
|
|
(section_filter_table[index].pid == pidP) && (section_filter_table[index].tid == tidP) &&
|
|
(section_filter_table[index].mask == maskP)) {
|
|
//debug("cIptvDevice::%s(%d): found %s", __FUNCTION__, deviceIndexM, section_filter_table[index].description);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int cIptvDevice::OpenFilter(u_short pidP, u_char tidP, u_char maskP)
|
|
{
|
|
// Check if disabled by user
|
|
if (!IptvConfig.GetSectionFiltering())
|
|
return -1;
|
|
// Lock
|
|
cMutexLock MutexLock(&mutexM);
|
|
// Blacklist check, refuse certain filters
|
|
if (IsBlackListed(pidP, tidP, maskP))
|
|
return -1;
|
|
// Search the next free filter slot
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (!secFiltersM[i]) {
|
|
//debug("cIptvDevice::%s(%d): pid=%d tid=%02X mask=%02X index=%d", __FUNCTION__, deviceIndexM, pidP, tidP, maskP, i);
|
|
secFiltersM[i] = new cIptvSectionFilter(deviceIndexM, pidP, tidP, maskP);
|
|
if (secFiltersM[i])
|
|
return i;
|
|
break;
|
|
}
|
|
}
|
|
// No free filter slot found
|
|
return -1;
|
|
}
|
|
|
|
int cIptvDevice::ReadFilter(int handleP, void *bufferP, size_t lengthP)
|
|
{
|
|
// Lock
|
|
cMutexLock MutexLock(&mutexM);
|
|
// ... and load
|
|
if (secFiltersM[handleP]) {
|
|
return secFiltersM[handleP]->Read(bufferP, lengthP);
|
|
//debug("cIptvDevice::%s(%d): handle=%d length=%d", __FUNCTION__, deviceIndexM, handleP, lengthP);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void cIptvDevice::CloseFilter(int handleP)
|
|
{
|
|
// Lock
|
|
cMutexLock MutexLock(&mutexM);
|
|
// ... and load
|
|
if (secFiltersM[handleP]) {
|
|
//debug("cIptvDevice::%s(%d): handle=%d", __FUNCTION__, deviceIndexM, handleP);
|
|
DeleteFilter(handleP);
|
|
}
|
|
}
|
|
|
|
bool cIptvDevice::OpenDvr(void)
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
isPacketDeliveredM = false;
|
|
tsBufferM->Clear();
|
|
ResetBuffering();
|
|
if (pIptvStreamerM)
|
|
pIptvStreamerM->Open();
|
|
if (sidScanEnabledM && pSidScannerM && IptvConfig.GetSectionFiltering())
|
|
pSidScannerM->Open();
|
|
isOpenDvrM = true;
|
|
return true;
|
|
}
|
|
|
|
void cIptvDevice::CloseDvr(void)
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
if (sidScanEnabledM && pSidScannerM && IptvConfig.GetSectionFiltering())
|
|
pSidScannerM->Close();
|
|
if (pIptvStreamerM)
|
|
pIptvStreamerM->Close();
|
|
isOpenDvrM = false;
|
|
}
|
|
|
|
bool cIptvDevice::HasLock(int timeoutMsP) const
|
|
{
|
|
//debug("cIptvDevice::%s(%d): timeoutMs=%d", __FUNCTION__, deviceIndexM, timeoutMsP);
|
|
return (!IsBuffering());
|
|
}
|
|
|
|
bool cIptvDevice::HasInternalCam(void)
|
|
{
|
|
//debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
return true;
|
|
}
|
|
|
|
void cIptvDevice::ResetBuffering(void)
|
|
{
|
|
debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
// Pad prefill to multiple of TS_SIZE
|
|
tsBufferPrefillM = (unsigned int)MEGABYTE(IptvConfig.GetTsBufferSize()) *
|
|
IptvConfig.GetTsBufferPrefillRatio() / 100;
|
|
tsBufferPrefillM -= (tsBufferPrefillM % TS_SIZE);
|
|
}
|
|
|
|
bool cIptvDevice::IsBuffering(void) const
|
|
{
|
|
//debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
if (tsBufferPrefillM && tsBufferM && tsBufferM->Available() < tsBufferPrefillM)
|
|
return true;
|
|
else
|
|
tsBufferPrefillM = 0;
|
|
return false;
|
|
}
|
|
|
|
bool cIptvDevice::GetTSPacket(uchar *&Data)
|
|
{
|
|
//debug("cIptvDevice::%s(%d)", __FUNCTION__, deviceIndexM);
|
|
if (tsBufferM && !IsBuffering()) {
|
|
if (isPacketDeliveredM) {
|
|
tsBufferM->Del(TS_SIZE);
|
|
isPacketDeliveredM = false;
|
|
// Update buffer statistics
|
|
AddBufferStatistic(TS_SIZE, tsBufferM->Available());
|
|
}
|
|
int Count = 0;
|
|
uchar *p = tsBufferM->Get(Count);
|
|
if (p && Count >= TS_SIZE) {
|
|
if (*p != TS_SYNC_BYTE) {
|
|
for (int i = 1; i < Count; i++) {
|
|
if (p[i] == TS_SYNC_BYTE) {
|
|
Count = i;
|
|
break;
|
|
}
|
|
}
|
|
tsBufferM->Del(Count);
|
|
error("Skipped %d bytes to sync on TS packet\n", Count);
|
|
return false;
|
|
}
|
|
isPacketDeliveredM = true;
|
|
Data = p;
|
|
// Update pid statistics
|
|
AddPidStatistic(ts_pid(p), payload(p));
|
|
// Send data also to dvr fifo
|
|
if (dvrFdM >= 0)
|
|
Count = (int)write(dvrFdM, p, TS_SIZE);
|
|
// Analyze incomplete streams with built-in pid analyzer
|
|
if (pidScanEnabledM && pPidScannerM)
|
|
pPidScannerM->Process(p);
|
|
// Lock
|
|
cMutexLock MutexLock(&mutexM);
|
|
// Run the data through all filters
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (secFiltersM[i])
|
|
secFiltersM[i]->Process(p);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
// Reduce cpu load by preventing busylooping
|
|
cCondWait::SleepMs(10);
|
|
Data = NULL;
|
|
return true;
|
|
}
|