1
0
mirror of https://github.com/rofafor/vdr-plugin-iptv.git synced 2023-10-10 11:37:03 +00:00

Compare commits

...

28 Commits

Author SHA1 Message Date
Rolf Ahrenberg
c975d458f5 Removed the disable_ca_updates patch. 2012-09-30 18:44:45 +03:00
Rolf Ahrenberg
11d864c16b Updated for vdr-1.7.30 and added support for source-specific multicasts (SSM).
Changed default external script directory from the configuration to the resource.
2012-09-30 18:42:40 +03:00
Rolf Ahrenberg
75d17b289b Added FreeBSD support (Thanks to Jürgen Lock). 2012-07-10 22:12:29 +03:00
Rolf Ahrenberg
547306b67c Updated translations. 2012-06-03 12:31:33 +03:00
Rolf Ahrenberg
763c83209d Updated README. 2012-06-03 12:24:09 +03:00
Rolf Ahrenberg
af85ef1703 Prepared for 1.0.0 release. 2012-06-02 21:57:29 +03:00
Rolf Ahrenberg
24ecadf414 Updated device selection. 2012-06-02 15:37:05 +03:00
Antti Seppälä
f4126b7e2c Try to read all data from udp socket at once
Existing implementation of udp read mechanism was giving up too easily.
Replaced it with version that tries more actively to drain the entire socket buffer.

Signed-off-by: Antti Seppälä <a.seppala@gmail.com>
2012-06-02 14:34:41 +03:00
Rolf Ahrenberg
c7cbde301b Tweaked reading from UDP sockets. 2012-06-02 13:32:28 +03:00
Rolf Ahrenberg
06506c41f6 Fixed TCP socket connection and silenced compilation warnings. 2012-04-26 21:32:20 +03:00
Rolf Ahrenberg
c33b05076a Incremented version number and updated HISTORY. 2012-04-26 00:29:46 +03:00
Rolf Ahrenberg
38bd9a21f6 Fixed response header parsing in HTTP protocol. 2012-04-26 00:27:24 +03:00
Rolf Ahrenberg
4c7b2ea69b Added cIptvDevice::DeviceType() and updated HISTORY. 2012-04-02 19:15:53 +03:00
Rolf Ahrenberg
c475b26515 Changed UDP protocol to always utilize the source address validation. 2012-04-02 00:35:32 +03:00
Rolf Ahrenberg
9145fb8bb8 Silenced a warning in the HTTP protocol. 2012-04-01 22:47:50 +03:00
Rolf Ahrenberg
7a8b2962b2 Added support for a service interface. 2012-04-01 22:46:08 +03:00
Rolf Ahrenberg
626d0402c2 Updated the disable ca updates patch. 2012-03-31 20:43:10 +03:00
Rolf Ahrenberg
b3b06e569f Fixed some channel switching bugs. 2012-03-31 20:32:04 +03:00
Rolf Ahrenberg
ff84c54d50 Fixed channel switching in UDP protocol. 2012-03-30 19:01:46 +03:00
Rolf Ahrenberg
4c39d8cf72 Updated device name. 2012-03-30 18:22:10 +03:00
Rolf Ahrenberg
0b790260fa Updated for vdr-1.7.27. 2012-03-25 16:42:06 +03:00
Rolf Ahrenberg
79113bbe09 Updated default CXXFLAGS. 2012-03-25 16:03:22 +03:00
Rolf Ahrenberg
30de763a4b Updated for vdr-1.7.26. 2012-03-10 23:22:20 +02:00
Rolf Ahrenberg
868865c47d Updated for vdr-1.7.25. 2012-03-03 15:18:59 +02:00
Rolf Ahrenberg
a73921041f Added a GIT tag into the version string. 2012-02-26 22:54:45 +02:00
Rolf Ahrenberg
49e3a9e23a Silenced compilation warnings. 2012-02-19 19:14:10 +02:00
Rolf Ahrenberg
73311873bc Updated Makefile. 2012-02-19 17:47:43 +02:00
Rolf Ahrenberg
816c357065 Updated the patch. 2012-02-19 17:43:43 +02:00
28 changed files with 492 additions and 257 deletions

30
HISTORY
View File

@@ -158,3 +158,33 @@ VDR Plugin 'iptv' Revision History
- Canonicalized the configuration directory. - Canonicalized the configuration directory.
- Added support for LDFLAGS. - Added support for LDFLAGS.
- Added cppcheck target into Makefile. - Added cppcheck target into Makefile.
2012-04-02: Version 0.5.1
- Updated for vdr-1.7.27.
- Updated Makefile.
- Silenced compilation warnings.
- Fixed channel switching bugs.
- Added support for a service interface.
- Changed UDP protocol to always utilize the source address
validation.
2012-04-26: Version 0.5.2
- Fixed connection problems in HTTP protocol.
2012-06-03: Version 1.0.0
- Optimized reading from UDP sockets.
- Fixed ProvidesChannel method.
2012-07-10: Version 1.0.1
- Added FreeBSD support (Thanks to Jürgen Lock).
2012-09-30: Version 1.1.0
- Updated for vdr-1.7.30.
- Added support for source-specific multicasts (SSM).
- Changed default external script directory from the
configuration to the resource.

View File

@@ -5,9 +5,15 @@
# Debugging on/off # Debugging on/off
#IPTV_DEBUG = 1 #IPTV_DEBUG = 1
# Default shell for EXT protocol
#IPTV_EXTSHELL = /bin/bash
# Strip debug symbols? Set eg. to /bin/true if not # Strip debug symbols? Set eg. to /bin/true if not
STRIP = strip STRIP = strip
# Install command
INSTALL = cp --remove-destination
# The official name of this plugin. # The official name of this plugin.
# This name will be used in the '-P...' option of VDR to load the plugin. # This name will be used in the '-P...' option of VDR to load the plugin.
# By default the main source file also carries this name. # By default the main source file also carries this name.
@@ -19,18 +25,19 @@ PLUGIN = iptv
### The version number of this plugin (taken from the main source file): ### The version number of this plugin (taken from the main source file):
VERSION = $(shell grep 'const char VERSION\[\] *=' $(PLUGIN).c | awk '{ print $$5 }' | sed -e 's/[";]//g') VERSION = $(shell grep 'const char VERSION\[\] *=' $(PLUGIN).c | awk '{ print $$5 }' | sed -e 's/[";]//g')
GITTAG = $(shell git describe --always 2>/dev/null)
### The C++ compiler and options: ### The C++ compiler and options:
CXX ?= g++ CXX ?= g++
CXXFLAGS ?= -fPIC -g -O3 -Wall -Wextra -Wswitch-default -Wfloat-equal -Wundef -Wpointer-arith -Wconversion -Wcast-align -Wredundant-decls -Wno-unused-parameter -Woverloaded-virtual -Wno-parentheses CXXFLAGS ?= -fPIC -g -O3 -Wall -Wextra -Wswitch-default -Wfloat-equal -Wundef -Wpointer-arith -Wconversion -Wcast-align -Wredundant-decls -Wno-unused-parameter -Werror=overloaded-virtual -Wno-parentheses
LDFLAGS ?= -Wl,--as-needed LDFLAGS ?= -Wl,--as-needed
### The directory environment: ### The directory environment:
VDRDIR = ../../.. VDRDIR ?= ../../..
LIBDIR = ../../lib LIBDIR ?= ../../lib
TMPDIR = /tmp TMPDIR ?= /tmp
### Make sure that necessary options are included: ### Make sure that necessary options are included:
@@ -59,6 +66,14 @@ ifdef IPTV_DEBUG
DEFINES += -DDEBUG DEFINES += -DDEBUG
endif endif
ifdef IPTV_EXTSHELL
DEFINES += -DEXTSHELL='"$(IPTV_EXTSHELL)"'
endif
ifneq ($(strip $(GITTAG)),)
DEFINES += -DGITVERSION='"-GIT-$(GITTAG)"'
endif
.PHONY: all all-redirect .PHONY: all all-redirect
all-redirect: all all-redirect: all
@@ -118,7 +133,7 @@ libvdr-$(PLUGIN).so: $(OBJS)
ifndef IPTV_DEBUG ifndef IPTV_DEBUG
@$(STRIP) $@ @$(STRIP) $@
endif endif
@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) @$(INSTALL) $@ $(LIBDIR)/$@.$(APIVERSION)
dist: $(I18Npo) clean dist: $(I18Npo) clean
@-rm -rf $(TMPDIR)/$(ARCHIVE) @-rm -rf $(TMPDIR)/$(ARCHIVE)

26
README
View File

@@ -44,7 +44,7 @@ cd /put/your/path/here/VDR/PLUGINS/src
tar -xzf /put/your/path/here/vdr-iptv-X.Y.Z.tgz tar -xzf /put/your/path/here/vdr-iptv-X.Y.Z.tgz
ln -s iptv-X.Y.Z iptv ln -s iptv-X.Y.Z iptv
cd /put/your/path/here/VDR cd /put/your/path/here/VDR
cp -R PLUGINS/src/iptv/iptv /path/to/vdrconf/plugins/ cp -R PLUGINS/src/iptv/iptv /path/to/vdrresource/plugins/
make make
make plugins make plugins
./vdr -P iptv ./vdr -P iptv
@@ -91,14 +91,15 @@ Configuration:
TV4;IPTV:40:S=1|P=0|F=EXT|U=iptvstream.sh|A=0:I:0:0:680:0:0:4:0:0:0 TV4;IPTV:40:S=1|P=0|F=EXT|U=iptvstream.sh|A=0:I:0:0:680:0:0:4:0:0:0
TV3;IPTV:30:S=0|P=1|F=FILE|U=/video/stream.ts|A=5:I:0:514:670:2321:0:3:0:0:0 TV3;IPTV:30:S=0|P=1|F=FILE|U=/video/stream.ts|A=5:I:0:514:670:2321:0:3:0:0:0
TV2;IPTV:20:S=0|P=1|F=HTTP|U=127.0.0.1/TS/2|A=3000:I:0:513:660:2321:0:2:0:0:0 TV2;IPTV:20:S=0|P=1|F=HTTP|U=127.0.0.1/TS/2|A=3000:I:0:513:660:2321:0:2:0:0:0
TV1;IPTV:10:S=1|P=0|F=UDP|U=127.0.0.1@127.0.0.1|A=1234:I:0:512:650:2321:0:1:0:0:0
TV1;IPTV:10:S=1|P=0|F=UDP|U=127.0.0.1|A=1234:I:0:512:650:2321:0:1:0:0:0 TV1;IPTV:10:S=1|P=0|F=UDP|U=127.0.0.1|A=1234:I:0:512:650:2321:0:1:0:0:0
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | Source type ("I") | | | | | | Source type ("I")
| | | | | Stream parameter (multicast port | | | | | Stream parameter (multicast port
| | | | | number, HTTP port number, file delay | | | | | number, HTTP port number, file delay
| | | | | (ms), script parameter) | | | | | (ms), script parameter)
| | | | Stream address (multicast address, URL, file | | | | Stream address (multicast source@group address,
| | | | location, script location) | | | | URL, file location, script location)
| | | Stream protocol ("UDP", "HTTP", "FILE", "EXT") | | | Stream protocol ("UDP", "HTTP", "FILE", "EXT")
| | Pid scanner ("0" disable, "1" enable) | | Pid scanner ("0" disable, "1" enable)
| Section id (Sid/Nid/Tid) scanner ("0" disable, "1" enable) | Section id (Sid/Nid/Tid) scanner ("0" disable, "1" enable)
@@ -106,19 +107,22 @@ Configuration:
- UDP multicast rules for iptables firewall - UDP multicast rules for iptables firewall
# Multicast UDP -packets # Multicast UDP packets
iptables -A INPUT -i eth0 -p udp -d 224.0.0.0/4 --dport 1234 -j ACCEPT iptables -A INPUT -i eth0 -p udp -d 224.0.0.0/4 --dport 1234 -j ACCEPT
# IGMP required by multicasts # IGMP required by multicasts
iptables -A INPUT -i eth0 -p igmp -d 224.0.0.0/4 -j ACCEPT iptables -A INPUT -i eth0 -p igmp -d 224.0.0.0/4 -j ACCEPT
# Default routing for multicast
route add -net 224.0.0.0 netmask 224.0.0.0 eth0
External streaming: External streaming:
- To watch an externally received channel add an EXT entry to channels.conf - To watch an externally received channel add an EXT entry to channels.conf
and specify a script name and parameter. The specified script is executed and specify a script name and parameter. The specified script is executed
from plugin configuration directory when VDR tunes to the channel. The from plugin resource directory when VDR tunes to the channel. The specified
specified script parameter is passed to the script and it can be used to script parameter is passed to the script and it can be used to select for
select for example between different URLs. example between different URLs.
- When an EXT channel is opened the IPTV plugin opens an UDP listening port - When an EXT channel is opened the IPTV plugin opens an UDP listening port
on the localhost. The external script is responsible for supplying IPTV on the localhost. The external script is responsible for supplying IPTV
@@ -161,12 +165,14 @@ Notes:
- EIT scanning functionality can be disabled for all IPTV channels by applying - EIT scanning functionality can be disabled for all IPTV channels by applying
the "disable_eitscan" patch to the VDR. the "disable_eitscan" patch to the VDR.
- Source address validation can be enabled for UDP protocol separated by
adding the source address after a ';' character: "U=239.192.0.1;239.192.0.2"
- Section id and pid scanners should be disabled after the correct data is - Section id and pid scanners should be disabled after the correct data is
found. This can be made via VDR's channel editor. found. This can be made via VDR's channel editor.
- Source-specific multicast (SSM) can be enabled by defining both the source
address and the group address separated by a '@' character. This will use
IGMP v3 protocol:
"U=<source address>@<group address>"
Acknowledgements: Acknowledgements:
- The IPTV section filtering code is derived from Linux kernel. - The IPTV section filtering code is derived from Linux kernel.

View File

@@ -30,6 +30,8 @@
#define IPTV_DEVICE_INFO_GENERAL 1 #define IPTV_DEVICE_INFO_GENERAL 1
#define IPTV_DEVICE_INFO_PIDS 2 #define IPTV_DEVICE_INFO_PIDS 2
#define IPTV_DEVICE_INFO_FILTERS 3 #define IPTV_DEVICE_INFO_FILTERS 3
#define IPTV_DEVICE_INFO_PROTOCOL 4
#define IPTV_DEVICE_INFO_BITRATE 5
#define IPTV_STATS_ACTIVE_PIDS_COUNT 10 #define IPTV_STATS_ACTIVE_PIDS_COUNT 10
#define IPTV_STATS_ACTIVE_FILTERS_COUNT 10 #define IPTV_STATS_ACTIVE_FILTERS_COUNT 10
@@ -37,13 +39,14 @@
#define SECTION_FILTER_TABLE_SIZE 7 #define SECTION_FILTER_TABLE_SIZE 7
#define ERROR_IF_FUNC(exp, errstr, func, ret) \ #define ERROR_IF_FUNC(exp, errstr, func, ret) \
do { \ do { \
if (exp) { \ if (exp) { \
char tmp[64]; \ char tmp[64]; \
error(errstr": %s", strerror_r(errno, tmp, sizeof(tmp))); \ strerror_r(errno, tmp, sizeof(tmp)); \
func; \ error(errstr": %s", tmp); \
ret; \ func; \
} \ ret; \
} \
} while (0) } while (0)

View File

@@ -42,6 +42,6 @@ void cIptvConfig::SetDisabledFilters(unsigned int Index, int Number)
void cIptvConfig::SetConfigDirectory(const char *directoryP) void cIptvConfig::SetConfigDirectory(const char *directoryP)
{ {
debug("cIptvConfig::SetConfigDirectory(%s)", directoryP); debug("cIptvConfig::SetConfigDirectory(%s)\n", directoryP);
ERROR_IF(!realpath(directoryP, configDirectory), "Cannot canonicalize configuration directory"); ERROR_IF(!realpath(directoryP, configDirectory), "Cannot canonicalize configuration directory");
} }

View File

@@ -123,7 +123,7 @@ cIptvDevice *cIptvDevice::GetIptvDevice(int CardIndex)
cString cIptvDevice::GetGeneralInformation(void) cString cIptvDevice::GetGeneralInformation(void)
{ {
//debug("cIptvDevice::GetGeneralInformation(%d)\n", deviceIndex); //debug("cIptvDevice::GetGeneralInformation(%d)\n", deviceIndex);
return cString::sprintf("IPTV device: %d\nCardIndex: %d\n%s%s%sChannel: %s", return cString::sprintf("IPTV device: %d\nCardIndex: %d\nStream: %s\nStream bitrate: %s\n%sChannel: %s",
deviceIndex, CardIndex(), deviceIndex, CardIndex(),
pIptvStreamer ? *pIptvStreamer->GetInformation() : "", pIptvStreamer ? *pIptvStreamer->GetInformation() : "",
pIptvStreamer ? *pIptvStreamer->GetStreamerStatistic() : "", pIptvStreamer ? *pIptvStreamer->GetStreamerStatistic() : "",
@@ -169,6 +169,12 @@ cString cIptvDevice::GetInformation(unsigned int Page)
case IPTV_DEVICE_INFO_FILTERS: case IPTV_DEVICE_INFO_FILTERS:
info = GetFiltersInformation(); info = GetFiltersInformation();
break; break;
case IPTV_DEVICE_INFO_PROTOCOL:
info = pIptvStreamer ? *pIptvStreamer->GetInformation() : "";
break;
case IPTV_DEVICE_INFO_BITRATE:
info = pIptvStreamer ? *pIptvStreamer->GetStreamerStatistic() : "";
break;
default: default:
info = cString::sprintf("%s%s%s", info = cString::sprintf("%s%s%s",
*GetGeneralInformation(), *GetGeneralInformation(),
@@ -179,6 +185,18 @@ cString cIptvDevice::GetInformation(unsigned int Page)
return info; return info;
} }
cString cIptvDevice::DeviceType(void) const
{
debug("cIptvDevice::DeviceType(%d)\n", deviceIndex);
return "IPTV";
}
cString cIptvDevice::DeviceName(void) const
{
debug("cIptvDevice::DeviceName(%d)\n", deviceIndex);
return cString::sprintf("IPTV %d", deviceIndex);
}
int cIptvDevice::SignalStrength(void) const int cIptvDevice::SignalStrength(void) const
{ {
debug("cIptvDevice::SignalStrength(%d)\n", deviceIndex); debug("cIptvDevice::SignalStrength(%d)\n", deviceIndex);
@@ -206,11 +224,20 @@ bool cIptvDevice::ProvidesTransponder(const cChannel *Channel) const
bool cIptvDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const bool cIptvDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const
{ {
bool result = false; bool result = false;
bool needsDetachReceivers = Receiving(true) && Channel && !(Channel->GetChannelID() == channelId); bool hasPriority = Priority == IDLEPRIORITY || Priority > this->Priority();
bool needsDetachReceivers = false;
debug("cIptvDevice::ProvidesChannel(%d)\n", deviceIndex); debug("cIptvDevice::ProvidesChannel(%d)\n", deviceIndex);
if (ProvidesTransponder(Channel))
result = true; if (Channel && ProvidesTransponder(Channel)) {
result = hasPriority;
if (Receiving()) {
if (Channel->GetChannelID() == channelId)
result = true;
else
needsDetachReceivers = Receiving();
}
}
if (NeedsDetachReceivers) if (NeedsDetachReceivers)
*NeedsDetachReceivers = needsDetachReceivers; *NeedsDetachReceivers = needsDetachReceivers;
return result; return result;
@@ -316,26 +343,37 @@ int cIptvDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) { for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
if (!secfilters[i]) { if (!secfilters[i]) {
//debug("cIptvDevice::OpenFilter(%d): Pid=%d Tid=%02X Mask=%02X Index=%d\n", deviceIndex, Pid, Tid, Mask, i); //debug("cIptvDevice::OpenFilter(%d): Pid=%d Tid=%02X Mask=%02X Index=%d\n", deviceIndex, Pid, Tid, Mask, i);
secfilters[i] = new cIptvSectionFilter(deviceIndex, i, Pid, Tid, Mask); secfilters[i] = new cIptvSectionFilter(deviceIndex, Pid, Tid, Mask);
return secfilters[i]->GetReadDesc(); if (secfilters[i])
return i;
break;
} }
} }
// No free filter slot found // No free filter slot found
return -1; return -1;
} }
int cIptvDevice::ReadFilter(int Handle, void *Buffer, size_t Length)
{
// Lock
cMutexLock MutexLock(&mutex);
// ... and load
if (secfilters[Handle]) {
return secfilters[Handle]->Read(Buffer, Length);
//debug("cIptvDevice::ReadFilter(%d): %d %d\n", deviceIndex, Handle, Length);
}
return 0;
}
void cIptvDevice::CloseFilter(int Handle) void cIptvDevice::CloseFilter(int Handle)
{ {
// Lock // Lock
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
// Search the filter for deletion // ... and load
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) { if (secfilters[Handle]) {
if (secfilters[i] && (Handle == secfilters[i]->GetReadDesc())) { //debug("cIptvDevice::CloseFilter(%d): %d\n", deviceIndex, Handle);
//debug("cIptvDevice::CloseFilter(%d): %d\n", deviceIndex, Handle); DeleteFilter(Handle);
DeleteFilter(i); }
break;
}
}
} }
bool cIptvDevice::OpenDvr(void) bool cIptvDevice::OpenDvr(void)
@@ -368,6 +406,12 @@ bool cIptvDevice::HasLock(int TimeoutMs)
return (!IsBuffering()); return (!IsBuffering());
} }
bool cIptvDevice::HasInternalCam(void)
{
//debug("cIptvDevice::HasInternalCam(%d)\n", deviceIndex);
return true;
}
void cIptvDevice::ResetBuffering(void) void cIptvDevice::ResetBuffering(void)
{ {
debug("cIptvDevice::ResetBuffering(%d)\n", deviceIndex); debug("cIptvDevice::ResetBuffering(%d)\n", deviceIndex);

View File

@@ -76,6 +76,9 @@ private:
bool IsBlackListed(u_short Pid, u_char Tid, u_char Mask) const; bool IsBlackListed(u_short Pid, u_char Tid, u_char Mask) const;
// for channel info // for channel info
public:
virtual cString DeviceType(void) const;
virtual cString DeviceName(void) const;
virtual int SignalStrength(void) const; virtual int SignalStrength(void) const;
virtual int SignalQuality(void) const; virtual int SignalQuality(void) const;
@@ -99,11 +102,16 @@ protected:
// for section filtering // for section filtering
public: public:
virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask); virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
virtual int ReadFilter(int Handle, void *Buffer, size_t Length);
virtual void CloseFilter(int Handle); virtual void CloseFilter(int Handle);
// for transponder lock // for transponder lock
public: public:
virtual bool HasLock(int); virtual bool HasLock(int);
// for common interface
public:
virtual bool HasInternalCam(void);
}; };
#endif // __IPTV_DEVICE_H #endif // __IPTV_DEVICE_H

27
iptv.c
View File

@@ -11,12 +11,17 @@
#include "config.h" #include "config.h"
#include "setup.h" #include "setup.h"
#include "device.h" #include "device.h"
#include "iptvservice.h"
#if defined(APIVERSNUM) && APIVERSNUM < 10721 #if defined(APIVERSNUM) && APIVERSNUM < 10730
#error "VDR-1.7.21 API version or greater is required!" #error "VDR-1.7.30 API version or greater is required!"
#endif #endif
const char VERSION[] = "0.5.0"; #ifndef GITVERSION
#define GITVERSION ""
#endif
const char VERSION[] = "1.1.0" GITVERSION;
static const char DESCRIPTION[] = trNOOP("Experience the IPTV"); static const char DESCRIPTION[] = trNOOP("Experience the IPTV");
class cPluginIptv : public cPlugin { class cPluginIptv : public cPlugin {
@@ -94,7 +99,7 @@ bool cPluginIptv::Initialize(void)
{ {
debug("cPluginIptv::Initialize()\n"); debug("cPluginIptv::Initialize()\n");
// Initialize any background activities the plugin shall perform. // Initialize any background activities the plugin shall perform.
IptvConfig.SetConfigDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N)); IptvConfig.SetConfigDirectory(cPlugin::ResourceDirectory(PLUGIN_NAME_I18N));
return cIptvDevice::Initialize(deviceCount); return cIptvDevice::Initialize(deviceCount);
} }
@@ -196,8 +201,18 @@ bool cPluginIptv::SetupParse(const char *Name, const char *Value)
bool cPluginIptv::Service(const char *Id, void *Data) bool cPluginIptv::Service(const char *Id, void *Data)
{ {
//debug("cPluginIptv::Service()\n"); debug("cPluginIptv::Service()\n");
// Handle custom service requests from other plugins if (strcmp(Id,"IptvService-v1.0") == 0) {
if (Data) {
IptvService_v1_0 *data = (IptvService_v1_0*)Data;
cIptvDevice *dev = cIptvDevice::GetIptvDevice(data->cardIndex);
if (!dev)
return false;
data->protocol = dev->GetInformation(IPTV_DEVICE_INFO_PROTOCOL);
data->bitrate = dev->GetInformation(IPTV_DEVICE_INFO_BITRATE);
}
return true;
}
return false; return false;
} }

22
iptvservice.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* iptvservice.h: IPTV plugin for the Video Disk Recorder
*
* See the README file for copyright information and how to reach the author.
*
*/
#ifndef __IPTVSERVICE_H
#define __IPTVSERVICE_H
#include <vdr/tools.h>
#define stIptv ('I' << 24)
struct IptvService_v1_0 {
unsigned int cardIndex;
cString protocol;
cString bitrate;
};
#endif //__IPTVSERVICE_H

View File

@@ -1,11 +0,0 @@
diff -Nru vdr-1.7.23-vanilla/pat.c vdr-1.7.23-disable_ca_updates/pat.c
--- vdr-1.7.23-vanilla/pat.c 2012-01-15 20:26:29.000000000 +0200
+++ vdr-1.7.23-disable_ca_updates/pat.c 2012-02-02 18:48:23.000000000 +0200
@@ -537,6 +537,7 @@
}
if (Setup.UpdateChannels >= 2) {
Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
+ if (!cSource::IsType(Channel->Source(), 'I'))
Channel->SetCaIds(CaDescriptors->CaIds());
Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds);
}

View File

@@ -5,10 +5,10 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: vdr-iptv 0.5.0\n" "Project-Id-Version: vdr-iptv 1.0.0\n"
"Report-Msgid-Bugs-To: <see README>\n" "Report-Msgid-Bugs-To: <see README>\n"
"POT-Creation-Date: 2012-02-02 02:02+0300\n" "POT-Creation-Date: 2012-06-03 06:03+0300\n"
"PO-Revision-Date: 2012-02-02 02:02+0300\n" "PO-Revision-Date: 2012-06-03 06:03+0300\n"
"Last-Translator: Tobias Grimm <tg@e-tobi.net>\n" "Last-Translator: Tobias Grimm <tg@e-tobi.net>\n"
"Language-Team: German <vdr@linuxtv.org>\n" "Language-Team: German <vdr@linuxtv.org>\n"
"Language: de\n" "Language: de\n"

View File

@@ -5,10 +5,10 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: vdr-iptv 0.5.0\n" "Project-Id-Version: vdr-iptv 1.0.0\n"
"Report-Msgid-Bugs-To: <see README>\n" "Report-Msgid-Bugs-To: <see README>\n"
"POT-Creation-Date: 2012-02-02 02:02+0300\n" "POT-Creation-Date: 2012-06-03 06:03+0300\n"
"PO-Revision-Date: 2012-02-02 02:02+0300\n" "PO-Revision-Date: 2012-06-03 06:03+0300\n"
"Last-Translator: Rolf Ahrenberg\n" "Last-Translator: Rolf Ahrenberg\n"
"Language-Team: Finnish <vdr@linuxtv.org>\n" "Language-Team: Finnish <vdr@linuxtv.org>\n"
"Language: fi\n" "Language: fi\n"

View File

@@ -6,10 +6,10 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: vdr-iptv 0.5.0\n" "Project-Id-Version: vdr-iptv 1.0.0\n"
"Report-Msgid-Bugs-To: <see README>\n" "Report-Msgid-Bugs-To: <see README>\n"
"POT-Creation-Date: 2012-02-02 02:02+0300\n" "POT-Creation-Date: 2012-06-03 06:03+0300\n"
"PO-Revision-Date: 2010-09-09 09:09+0300\n" "PO-Revision-Date: 2012-06-03 06:03+0300\n"
"Last-Translator: NIVAL Michaël <mnival@club-internet.fr>\n" "Last-Translator: NIVAL Michaël <mnival@club-internet.fr>\n"
"Language-Team: French <vdr@linuxtv.org>\n" "Language-Team: French <vdr@linuxtv.org>\n"
"Language: fr\n" "Language: fr\n"

View File

@@ -5,10 +5,10 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: vdr-iptv 0.5.0\n" "Project-Id-Version: vdr-iptv 1.0.0\n"
"Report-Msgid-Bugs-To: <see README>\n" "Report-Msgid-Bugs-To: <see README>\n"
"POT-Creation-Date: 2012-02-02 02:02+0300\n" "POT-Creation-Date: 2012-06-03 06:03+0300\n"
"PO-Revision-Date: 2012-02-02 02:02+0300\n" "PO-Revision-Date: 2012-06-03 06:03+0300\n"
"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n" "Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
"Language-Team: Italian <vdr@linuxtv.org>\n" "Language-Team: Italian <vdr@linuxtv.org>\n"
"Language: it\n" "Language: it\n"

View File

@@ -5,10 +5,10 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: vdr-iptv 0.5.0\n" "Project-Id-Version: vdr-iptv 1.0.0\n"
"Report-Msgid-Bugs-To: <see README>\n" "Report-Msgid-Bugs-To: <see README>\n"
"POT-Creation-Date: 2012-02-02 02:02+0300\n" "POT-Creation-Date: 2012-06-03 06:03+0300\n"
"PO-Revision-Date: 2012-02-02 02:02+0300\n" "PO-Revision-Date: 2012-06-03 06:03+0300\n"
"Last-Translator: Carel\n" "Last-Translator: Carel\n"
"Language-Team: Dutch <vdr@linuxtv.org>\n" "Language-Team: Dutch <vdr@linuxtv.org>\n"
"Language: nl\n" "Language: nl\n"

View File

@@ -5,10 +5,10 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: vdr-iptv 0.5.0\n" "Project-Id-Version: vdr-iptv 1.0.0\n"
"Report-Msgid-Bugs-To: <see README>\n" "Report-Msgid-Bugs-To: <see README>\n"
"POT-Creation-Date: 2012-02-02 02:02+0300\n" "POT-Creation-Date: 2012-06-03 06:03+0300\n"
"PO-Revision-Date: 2010-09-09 09:09+0300\n" "PO-Revision-Date: 2012-06-03 06:03+0300\n"
"Last-Translator: Alexander Gross <Bikalexander@gmail.com>\n" "Last-Translator: Alexander Gross <Bikalexander@gmail.com>\n"
"Language-Team: Russian <vdr@linuxtv.org>\n" "Language-Team: Russian <vdr@linuxtv.org>\n"
"Language: ru\n" "Language: ru\n"

View File

@@ -19,10 +19,15 @@
#include "config.h" #include "config.h"
#include "protocolext.h" #include "protocolext.h"
#ifndef EXTSHELL
#define EXTSHELL "/bin/bash"
#endif
cIptvProtocolExt::cIptvProtocolExt() cIptvProtocolExt::cIptvProtocolExt()
: pid(-1), : pid(-1),
scriptFile(""), scriptFile(""),
scriptParameter(0) scriptParameter(0),
streamPort(0)
{ {
debug("cIptvProtocolExt::cIptvProtocolExt()\n"); debug("cIptvProtocolExt::cIptvProtocolExt()\n");
} }
@@ -53,9 +58,11 @@ void cIptvProtocolExt::ExecuteScript(void)
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++) for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
close(i); close(i);
// Execute the external script // Execute the external script
cString cmd = cString::sprintf("%s %d %d", *scriptFile, scriptParameter, socketPort); cString cmd = cString::sprintf("%s %d %d", *scriptFile, scriptParameter, streamPort);
debug("cIptvProtocolExt::ExecuteScript(child): %s\n", *cmd); debug("cIptvProtocolExt::ExecuteScript(child): %s\n", *cmd);
if (execl("/bin/bash", "sh", "-c", *cmd, (char *)NULL) == -1) { // Create a new session for a process group
ERROR_IF_RET(setsid() == -1, "setsid()", _exit(-1));
if (execl(EXTSHELL, "sh", "-c", *cmd, (char *)NULL) == -1) {
error("Script execution failed: %s", *cmd); error("Script execution failed: %s", *cmd);
_exit(-1); _exit(-1);
} }
@@ -74,27 +81,36 @@ void cIptvProtocolExt::TerminateScript(void)
if (pid > 0) { if (pid > 0) {
const unsigned int timeoutms = 100; const unsigned int timeoutms = 100;
unsigned int waitms = 0; unsigned int waitms = 0;
siginfo_t waitStatus;
bool waitOver = false; bool waitOver = false;
// signal and wait for termination // Signal and wait for termination
int retval = kill(pid, SIGINT); int retval = killpg(pid, SIGINT);
ERROR_IF_RET(retval < 0, "kill()", waitOver = true); ERROR_IF_RET(retval < 0, "kill()", waitOver = true);
while (!waitOver) { while (!waitOver) {
retval = 0; retval = 0;
waitms += timeoutms; waitms += timeoutms;
if ((waitms % 2000) == 0) { if ((waitms % 2000) == 0) {
error("Script '%s' won't terminate - killing it!", *scriptFile); error("Script '%s' won't terminate - killing it!", *scriptFile);
kill(pid, SIGKILL); killpg(pid, SIGKILL);
} }
// Clear wait status to make sure child exit status is accessible // Clear wait status to make sure child exit status is accessible
// and wait for child termination
#ifdef __FreeBSD__
int waitStatus = 0;
retval = waitpid(pid, &waitStatus, WNOHANG);
#else // __FreeBSD__
siginfo_t waitStatus;
memset(&waitStatus, '\0', sizeof(waitStatus)); memset(&waitStatus, '\0', sizeof(waitStatus));
// Wait for child termination
retval = waitid(P_PID, pid, &waitStatus, (WNOHANG | WEXITED)); retval = waitid(P_PID, pid, &waitStatus, (WNOHANG | WEXITED));
#endif // __FreeBSD__
ERROR_IF_RET(retval < 0, "waitid()", waitOver = true); ERROR_IF_RET(retval < 0, "waitid()", waitOver = true);
// These are the acceptable conditions under which child exit is // These are the acceptable conditions under which child exit is
// regarded as successful // regarded as successful
#ifdef __FreeBSD__
if (retval > 0 && (WIFEXITED(waitStatus) || WIFSIGNALED(waitStatus))) {
#else // __FreeBSD__
if (!retval && waitStatus.si_pid && (waitStatus.si_pid == pid) && if (!retval && waitStatus.si_pid && (waitStatus.si_pid == pid) &&
((waitStatus.si_code == CLD_EXITED) || (waitStatus.si_code == CLD_KILLED))) { ((waitStatus.si_code == CLD_EXITED) || (waitStatus.si_code == CLD_KILLED))) {
#endif // __FreeBSD__
debug("Child (%d) exited as expected\n", pid); debug("Child (%d) exited as expected\n", pid);
waitOver = true; waitOver = true;
} }
@@ -113,7 +129,7 @@ bool cIptvProtocolExt::Open(void)
if (!strlen(*scriptFile)) if (!strlen(*scriptFile))
return false; return false;
// Create the listening socket // Create the listening socket
OpenSocket(socketPort); OpenSocket(streamPort);
// Execute the external script // Execute the external script
ExecuteScript(); ExecuteScript();
isActive = true; isActive = true;
@@ -149,7 +165,7 @@ bool cIptvProtocolExt::Set(const char* Location, const int Parameter, const int
} }
scriptParameter = Parameter; scriptParameter = Parameter;
// Update listen port // Update listen port
socketPort = IptvConfig.GetExtProtocolBasePort() + Index; streamPort = IptvConfig.GetExtProtocolBasePort() + Index;
} }
return true; return true;
} }

View File

@@ -17,6 +17,7 @@ private:
int pid; int pid;
cString scriptFile; cString scriptFile;
int scriptParameter; int scriptParameter;
int streamPort;
private: private:
void TerminateScript(void); void TerminateScript(void);

View File

@@ -20,7 +20,8 @@
cIptvProtocolHttp::cIptvProtocolHttp() cIptvProtocolHttp::cIptvProtocolHttp()
: streamAddr(strdup("")), : streamAddr(strdup("")),
streamPath(strdup("/")) streamPath(strdup("/")),
streamPort(0)
{ {
debug("cIptvProtocolHttp::cIptvProtocolHttp()\n"); debug("cIptvProtocolHttp::cIptvProtocolHttp()\n");
} }
@@ -41,7 +42,7 @@ bool cIptvProtocolHttp::Connect(void)
// Check that stream address is valid // Check that stream address is valid
if (!isActive && !isempty(streamAddr) && !isempty(streamPath)) { if (!isActive && !isempty(streamAddr) && !isempty(streamPath)) {
// Ensure that socket is valid and connect // Ensure that socket is valid and connect
OpenSocket(socketPort, streamAddr); OpenSocket(streamPort, streamAddr);
if (!ConnectSocket()) { if (!ConnectSocket()) {
CloseSocket(); CloseSocket();
return false; return false;
@@ -127,15 +128,19 @@ bool cIptvProtocolHttp::ProcessHeaders(void)
{ {
debug("cIptvProtocolHttp::ProcessHeaders()\n"); debug("cIptvProtocolHttp::ProcessHeaders()\n");
unsigned int lineLength = 0; unsigned int lineLength = 0;
int response = 0; int version = 0, response = 0;
bool responseFound = false; bool responseFound = false;
char fmt[32];
char buf[4096]; char buf[4096];
// Generate HTTP response format string with 2 arguments
snprintf(fmt, sizeof(fmt), "HTTP/1.%%%zui %%%zui ", sizeof(version) - 1, sizeof(response) - 1);
while (!responseFound || lineLength != 0) { while (!responseFound || lineLength != 0) {
memset(buf, '\0', sizeof(buf)); memset(buf, '\0', sizeof(buf));
if (!GetHeaderLine(buf, sizeof(buf), lineLength)) if (!GetHeaderLine(buf, sizeof(buf), lineLength))
return false; return false;
if (!responseFound && sscanf(buf, "HTTP/1.%*i %i ", &response) != 1) { if (!responseFound && sscanf(buf, fmt, &version, &response) != 2) {
error("Expected HTTP header not found\n"); error("Expected HTTP header not found\n");
continue; continue;
} }
@@ -185,8 +190,8 @@ bool cIptvProtocolHttp::Set(const char* Location, const int Parameter, const int
} }
else else
streamPath = strcpyrealloc(streamPath, "/"); streamPath = strcpyrealloc(streamPath, "/");
socketPort = Parameter; streamPort = Parameter;
//debug("http://%s:%d%s\n", streamAddr, socketPort, streamPath); //debug("http://%s:%d%s\n", streamAddr, streamPort, streamPath);
// Re-connect the socket // Re-connect the socket
Connect(); Connect();
} }
@@ -196,5 +201,5 @@ bool cIptvProtocolHttp::Set(const char* Location, const int Parameter, const int
cString cIptvProtocolHttp::GetInformation(void) cString cIptvProtocolHttp::GetInformation(void)
{ {
//debug("cIptvProtocolHttp::GetInformation()"); //debug("cIptvProtocolHttp::GetInformation()");
return cString::sprintf("http://%s:%d%s", streamAddr, socketPort, streamPath); return cString::sprintf("http://%s:%d%s", streamAddr, streamPort, streamPath);
} }

View File

@@ -16,6 +16,7 @@ class cIptvProtocolHttp : public cIptvTcpSocket, public cIptvProtocolIf {
private: private:
char* streamAddr; char* streamAddr;
char* streamPath; char* streamPath;
int streamPort;
private: private:
bool Connect(void); bool Connect(void);

View File

@@ -19,8 +19,10 @@
#include "socket.h" #include "socket.h"
cIptvProtocolUdp::cIptvProtocolUdp() cIptvProtocolUdp::cIptvProtocolUdp()
: streamAddr(strdup("")), : isIGMPv3(false),
sourceAddr(strdup("")) sourceAddr(strdup("")),
streamAddr(strdup("")),
streamPort(0)
{ {
debug("cIptvProtocolUdp::cIptvProtocolUdp()\n"); debug("cIptvProtocolUdp::cIptvProtocolUdp()\n");
} }
@@ -37,28 +39,29 @@ cIptvProtocolUdp::~cIptvProtocolUdp()
bool cIptvProtocolUdp::Open(void) bool cIptvProtocolUdp::Open(void)
{ {
debug("cIptvProtocolUdp::Open()\n"); debug("cIptvProtocolUdp::Open(): streamAddr=%s\n", streamAddr);
OpenSocket(socketPort, isempty(sourceAddr) ? INADDR_ANY : inet_addr(sourceAddr)); OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
if (!isempty(streamAddr)) { if (!isempty(streamAddr)) {
// Join a new multicast group // Join a new multicast group
JoinMulticast(inet_addr(streamAddr)); JoinMulticast();
} }
return true; return true;
} }
bool cIptvProtocolUdp::Close(void) bool cIptvProtocolUdp::Close(void)
{ {
debug("cIptvProtocolUdp::Close()\n"); debug("cIptvProtocolUdp::Close(): streamAddr=%s\n", streamAddr);
if (!isempty(streamAddr)) { if (!isempty(streamAddr)) {
// Drop the multicast group // Drop the multicast group
OpenSocket(socketPort, isempty(sourceAddr) ? INADDR_ANY : inet_addr(sourceAddr)); OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
DropMulticast(inet_addr(streamAddr)); DropMulticast();
} }
// Close the socket // Close the socket
CloseSocket(); CloseSocket();
// Reset stream and source addresses // Do NOT reset stream and source addresses
streamAddr = strcpyrealloc(streamAddr, ""); //sourceAddr = strcpyrealloc(sourceAddr, "");
sourceAddr = strcpyrealloc(sourceAddr, ""); //streamAddr = strcpyrealloc(streamAddr, "");
//streamPort = 0;
return true; return true;
} }
@@ -73,23 +76,28 @@ bool cIptvProtocolUdp::Set(const char* Location, const int Parameter, const int
if (!isempty(Location)) { if (!isempty(Location)) {
// Drop the multicast group // Drop the multicast group
if (!isempty(streamAddr)) { if (!isempty(streamAddr)) {
OpenSocket(socketPort, isempty(sourceAddr) ? INADDR_ANY : inet_addr(sourceAddr)); OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
DropMulticast(inet_addr(streamAddr)); DropMulticast();
} }
// Update stream address and port // Update stream address and port
streamAddr = strcpyrealloc(streamAddr, Location); streamAddr = strcpyrealloc(streamAddr, Location);
char *p = strstr(streamAddr, ";"); // <group address> or <source address>@<group address>
char *p = strstr(streamAddr, "@");
if (p) { if (p) {
sourceAddr = strcpyrealloc(sourceAddr, p + 1);
*p = 0; *p = 0;
sourceAddr = strcpyrealloc(sourceAddr, streamAddr);
streamAddr = strcpyrealloc(streamAddr, p + 1);
isIGMPv3 = true;
} }
else else {
sourceAddr = strcpyrealloc(sourceAddr, ""); sourceAddr = strcpyrealloc(sourceAddr, streamAddr);
socketPort = Parameter; isIGMPv3 = false;
}
streamPort = Parameter;
// Join a new multicast group // Join a new multicast group
if (!isempty(streamAddr)) { if (!isempty(streamAddr)) {
OpenSocket(socketPort, isempty(sourceAddr) ? INADDR_ANY : inet_addr(sourceAddr)); OpenSocket(streamPort, streamAddr, sourceAddr, isIGMPv3);
JoinMulticast(inet_addr(streamAddr)); JoinMulticast();
} }
} }
return true; return true;
@@ -98,5 +106,7 @@ bool cIptvProtocolUdp::Set(const char* Location, const int Parameter, const int
cString cIptvProtocolUdp::GetInformation(void) cString cIptvProtocolUdp::GetInformation(void)
{ {
//debug("cIptvProtocolUdp::GetInformation()"); //debug("cIptvProtocolUdp::GetInformation()");
return cString::sprintf("udp://%s:%d", streamAddr, socketPort); if (isIGMPv3)
return cString::sprintf("udp://%s@%s:%d", sourceAddr, streamAddr, streamPort);
return cString::sprintf("udp://%s:%d", streamAddr, streamPort);
} }

View File

@@ -14,8 +14,10 @@
class cIptvProtocolUdp : public cIptvUdpSocket, public cIptvProtocolIf { class cIptvProtocolUdp : public cIptvUdpSocket, public cIptvProtocolIf {
private: private:
char* streamAddr; bool isIGMPv3;
char* sourceAddr; char* sourceAddr;
char* streamAddr;
int streamPort;
public: public:
cIptvProtocolUdp(); cIptvProtocolUdp();

View File

@@ -5,10 +5,10 @@
* *
*/ */
#include "config.h"
#include "sectionfilter.h" #include "sectionfilter.h"
cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index, cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, uint16_t Pid, uint8_t Tid, uint8_t Mask)
uint16_t Pid, uint8_t Tid, uint8_t Mask)
: pusi_seen(0), : pusi_seen(0),
feedcc(0), feedcc(0),
doneq(0), doneq(0),
@@ -17,10 +17,9 @@ cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index,
seclen(0), seclen(0),
tsfeedp(0), tsfeedp(0),
pid(Pid), pid(Pid),
devid(DeviceIndex), devid(DeviceIndex)
id(Index)
{ {
//debug("cIptvSectionFilter::cIptvSectionFilter(%d, %d)\n", devid, id); //debug("cIptvSectionFilter::cIptvSectionFilter(%d, %d)\n", devid, pid);
int i; int i;
memset(secbuf_base, '\0', sizeof(secbuf_base)); memset(secbuf_base, '\0', sizeof(secbuf_base));
@@ -47,35 +46,30 @@ cIptvSectionFilter::cIptvSectionFilter(int DeviceIndex, int Index,
} }
doneq = local_doneq ? 1 : 0; doneq = local_doneq ? 1 : 0;
// Create sockets // Create filtering buffer
socket[0] = socket[1] = -1; ringbuffer = new cRingBufferLinear(KILOBYTE(128), 0, false, *cString::sprintf("IPTV SECTION %d/%d", devid, pid));
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socket) != 0) { if (ringbuffer)
char tmp[64]; ringbuffer->SetTimeouts(10, 10);
error("Opening section filter sockets failed (device=%d id=%d): %s\n", devid, id, strerror_r(errno, tmp, sizeof(tmp))); else
} error("Failed to allocate buffer for section filter (device=%d pid=%d): ", devid, pid);
else if ((fcntl(socket[0], F_SETFL, O_NONBLOCK) != 0) || (fcntl(socket[1], F_SETFL, O_NONBLOCK) != 0)) {
char tmp[64];
error("Setting section filter socket to non-blocking mode failed (device=%d id=%d): %s", devid, id, strerror_r(errno, tmp, sizeof(tmp)));
}
} }
cIptvSectionFilter::~cIptvSectionFilter() cIptvSectionFilter::~cIptvSectionFilter()
{ {
//debug("cIptvSectionFilter::~cIptvSectionfilter(%d, %d)\n", devid, id); //debug("cIptvSectionFilter::~cIptvSectionfilter(%d, %d)\n", devid, pid);
int tmp = socket[1]; DELETE_POINTER(ringbuffer);
socket[1] = -1;
if (tmp >= 0)
close(tmp);
tmp = socket[0];
socket[0] = -1;
if (tmp >= 0)
close(tmp);
secbuf = NULL; secbuf = NULL;
} }
int cIptvSectionFilter::GetReadDesc(void) int cIptvSectionFilter::Read(void *Data, size_t Length)
{ {
return socket[0]; int count = 0;
uchar *p = ringbuffer->Get(count);
if (p && count > 0) {
memcpy(Data, p, count);
ringbuffer->Del(count);
}
return count;
} }
inline uint16_t cIptvSectionFilter::GetLength(const uint8_t *Data) inline uint16_t cIptvSectionFilter::GetLength(const uint8_t *Data)
@@ -105,10 +99,10 @@ int cIptvSectionFilter::Filter(void)
if (doneq && !neq) if (doneq && !neq)
return 0; return 0;
// There is no data in the read socket, more can be written if (ringbuffer) {
if ((socket[0] >= 0) && (socket[1] >= 0) /*&& !select_single_desc(socket[0], 0, false)*/) { int len = ringbuffer->Put(secbuf, seclen);
ssize_t len = write(socket[1], secbuf, seclen); if (len != seclen)
ERROR_IF(len < 0, "write()"); ringbuffer->ReportOverflow(seclen - len);
// Update statistics // Update statistics
AddSectionStatistic(len, 1); AddSectionStatistic(len, 1);
} }
@@ -138,7 +132,7 @@ int cIptvSectionFilter::CopyDump(const uint8_t *buf, uint8_t len)
return 0; return 0;
memcpy(secbuf_base + tsfeedp, buf, len); memcpy(secbuf_base + tsfeedp, buf, len);
tsfeedp += len; tsfeedp = uint16_t(tsfeedp + len);
limit = tsfeedp; limit = tsfeedp;
if (limit > DMX_MAX_SECFEED_SIZE) if (limit > DMX_MAX_SECFEED_SIZE)
@@ -154,7 +148,7 @@ int cIptvSectionFilter::CopyDump(const uint8_t *buf, uint8_t len)
seclen = seclen_local; seclen = seclen_local;
if (pusi_seen) if (pusi_seen)
Feed(); Feed();
secbufp += seclen_local; secbufp = uint16_t(secbufp + seclen_local);
secbuf += seclen_local; secbuf += seclen_local;
} }
return 0; return 0;

View File

@@ -8,6 +8,9 @@
#ifndef __IPTV_SECTIONFILTER_H #ifndef __IPTV_SECTIONFILTER_H
#define __IPTV_SECTIONFILTER_H #define __IPTV_SECTIONFILTER_H
#ifdef __FreeBSD__
#include <sys/socket.h>
#endif // __FreeBSD__
#include <vdr/device.h> #include <vdr/device.h>
#include "common.h" #include "common.h"
@@ -33,8 +36,6 @@ private:
uint16_t pid; uint16_t pid;
int devid; int devid;
int id;
int socket[2];
uint8_t filter_value[DMX_MAX_FILTER_SIZE]; uint8_t filter_value[DMX_MAX_FILTER_SIZE];
uint8_t filter_mask[DMX_MAX_FILTER_SIZE]; uint8_t filter_mask[DMX_MAX_FILTER_SIZE];
@@ -43,6 +44,8 @@ private:
uint8_t maskandmode[DMX_MAX_FILTER_SIZE]; uint8_t maskandmode[DMX_MAX_FILTER_SIZE];
uint8_t maskandnotmode[DMX_MAX_FILTER_SIZE]; uint8_t maskandnotmode[DMX_MAX_FILTER_SIZE];
cRingBufferLinear *ringbuffer;
inline uint16_t GetLength(const uint8_t *Data); inline uint16_t GetLength(const uint8_t *Data);
void New(void); void New(void);
int Filter(void); int Filter(void);
@@ -51,11 +54,10 @@ private:
public: public:
// constructor & destructor // constructor & destructor
cIptvSectionFilter(int Index, int DeviceIndex, uint16_t Pid, cIptvSectionFilter(int DeviceIndex, uint16_t Pid, uint8_t Tid, uint8_t Mask);
uint8_t Tid, uint8_t Mask);
virtual ~cIptvSectionFilter(); virtual ~cIptvSectionFilter();
void Process(const uint8_t* Data); void Process(const uint8_t* Data);
int GetReadDesc(void); int Read(void *Buffer, size_t Length);
uint16_t GetPid(void) const { return pid; } uint16_t GetPid(void) const { return pid; }
}; };

265
socket.c
View File

@@ -7,6 +7,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <net/if.h>
#include <netdb.h> #include <netdb.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
@@ -18,8 +19,8 @@
#include "socket.h" #include "socket.h"
cIptvSocket::cIptvSocket() cIptvSocket::cIptvSocket()
: socketDesc(-1), : socketPort(0),
socketPort(0), socketDesc(-1),
isActive(false) isActive(false)
{ {
debug("cIptvSocket::cIptvSocket()\n"); debug("cIptvSocket::cIptvSocket()\n");
@@ -57,11 +58,13 @@ bool cIptvSocket::OpenSocket(const int Port, const bool isUdp)
// Allow multiple sockets to use the same PORT number // Allow multiple sockets to use the same PORT number
ERROR_IF_FUNC(setsockopt(socketDesc, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0, "setsockopt(SO_REUSEADDR)", ERROR_IF_FUNC(setsockopt(socketDesc, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0, "setsockopt(SO_REUSEADDR)",
CloseSocket(), return false); CloseSocket(), return false);
#ifndef __FreeBSD__
// Allow packet information to be fetched // Allow packet information to be fetched
ERROR_IF_FUNC(setsockopt(socketDesc, SOL_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0, "setsockopt(IP_PKTINFO)", ERROR_IF_FUNC(setsockopt(socketDesc, SOL_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0, "setsockopt(IP_PKTINFO)",
CloseSocket(), return false); CloseSocket(), return false);
#endif // __FreeBSD__
// Bind socket // Bind socket
memset(&sockAddr, '\0', sizeof(sockAddr)); memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET; sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons((uint16_t)(Port & 0xFFFF)); sockAddr.sin_port = htons((uint16_t)(Port & 0xFFFF));
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
@@ -87,9 +90,33 @@ void cIptvSocket::CloseSocket(void)
} }
} }
bool cIptvSocket::CheckAddress(const char *Addr, in_addr_t *InAddr)
{
if (InAddr) {
// First try only the IP address
*InAddr = htonl(inet_addr(Addr));
if (*InAddr == htonl(INADDR_NONE)) {
debug("Cannot convert %s directly to internet address\n", Addr);
// It may be a host name, get the name
struct hostent *host;
host = gethostbyname(Addr);
if (!host) {
char tmp[64];
error("gethostbyname() failed: %s is not valid address: %s", Addr, strerror_r(h_errno, tmp, sizeof(tmp)));
return false;
}
*InAddr = htonl(inet_addr(*host->h_addr_list));
}
return true;
}
return false;
}
// UDP socket class // UDP socket class
cIptvUdpSocket::cIptvUdpSocket() cIptvUdpSocket::cIptvUdpSocket()
: sourceAddr(INADDR_ANY) : streamAddr(htonl(INADDR_ANY)),
sourceAddr(htonl(INADDR_ANY)),
useIGMPv3(false)
{ {
debug("cIptvUdpSocket::cIptvUdpSocket()\n"); debug("cIptvUdpSocket::cIptvUdpSocket()\n");
} }
@@ -99,46 +126,95 @@ cIptvUdpSocket::~cIptvUdpSocket()
debug("cIptvUdpSocket::~cIptvUdpSocket()\n"); debug("cIptvUdpSocket::~cIptvUdpSocket()\n");
} }
bool cIptvUdpSocket::OpenSocket(const int Port, const in_addr_t SourceAddr) bool cIptvUdpSocket::OpenSocket(const int Port)
{ {
debug("cIptvUdpSocket::OpenSocket()\n"); debug("cIptvUdpSocket::OpenSocket()\n");
sourceAddr = SourceAddr; streamAddr = htonl(INADDR_ANY);
sourceAddr = htonl(INADDR_ANY);
useIGMPv3 = false;
return cIptvSocket::OpenSocket(Port, true);
}
bool cIptvUdpSocket::OpenSocket(const int Port, const char *StreamAddr, const char *SourceAddr, bool UseIGMPv3)
{
debug("cIptvUdpSocket::OpenSocket()\n");
CheckAddress(StreamAddr, &streamAddr);
CheckAddress(SourceAddr, &sourceAddr);
useIGMPv3 = UseIGMPv3;
return cIptvSocket::OpenSocket(Port, true); return cIptvSocket::OpenSocket(Port, true);
} }
void cIptvUdpSocket::CloseSocket(void) void cIptvUdpSocket::CloseSocket(void)
{ {
debug("cIptvUdpSocket::CloseSocket()\n"); debug("cIptvUdpSocket::CloseSocket()\n");
sourceAddr = INADDR_ANY; streamAddr = htonl(INADDR_ANY);
sourceAddr = htonl(INADDR_ANY);
useIGMPv3 = false;
cIptvSocket::CloseSocket(); cIptvSocket::CloseSocket();
} }
bool cIptvUdpSocket::JoinMulticast(const in_addr_t StreamAddr) bool cIptvUdpSocket::JoinMulticast(void)
{ {
debug("cIptvUdpSocket::JoinMulticast()\n"); debug("cIptvUdpSocket::JoinMulticast()\n");
// Check if socket exists // Check if socket exists
if (!isActive && (socketDesc >= 0)) { if (!isActive && (socketDesc >= 0)) {
// Join a new multicast group // Join a new multicast group
struct ip_mreq mreq; if (useIGMPv3) {
mreq.imr_multiaddr.s_addr = StreamAddr; // Source-specific multicast (SSM) is used
mreq.imr_interface.s_addr = htonl(INADDR_ANY); struct group_source_req gsr;
ERROR_IF_RET(setsockopt(socketDesc, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_ADD_MEMBERSHIP)", return false); struct sockaddr_in *grp;
struct sockaddr_in *src;
gsr.gsr_interface = 0; // if_nametoindex("any") ?
grp = (struct sockaddr_in*)&gsr.gsr_group;
grp->sin_family = AF_INET;
grp->sin_addr.s_addr = streamAddr;
grp->sin_port = 0;
src = (struct sockaddr_in*)&gsr.gsr_source;
src->sin_family = AF_INET;
src->sin_addr.s_addr = sourceAddr;
src->sin_port = 0;
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, MCAST_JOIN_SOURCE_GROUP, &gsr, sizeof(gsr)) < 0, "setsockopt(MCAST_JOIN_SOURCE_GROUP)", return false);
}
else {
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = streamAddr;
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_ADD_MEMBERSHIP)", return false);
}
// Update multicasting flag // Update multicasting flag
isActive = true; isActive = true;
} }
return true; return true;
} }
bool cIptvUdpSocket::DropMulticast(const in_addr_t StreamAddr) bool cIptvUdpSocket::DropMulticast(void)
{ {
debug("cIptvUdpSocket::DropMulticast()\n"); debug("cIptvUdpSocket::DropMulticast()\n");
// Check if socket exists // Check if socket exists
if (isActive && (socketDesc >= 0)) { if (isActive && (socketDesc >= 0)) {
// Drop the existing multicast group // Drop the existing multicast group
struct ip_mreq mreq; if (useIGMPv3) {
mreq.imr_multiaddr.s_addr = StreamAddr; // Source-specific multicast (SSM) is used
mreq.imr_interface.s_addr = htonl(INADDR_ANY); struct group_source_req gsr;
ERROR_IF_RET(setsockopt(socketDesc, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_DROP_MEMBERSHIP)", return false); struct sockaddr_in *grp;
struct sockaddr_in *src;
gsr.gsr_interface = 0; // if_nametoindex("any") ?
grp = (struct sockaddr_in*)&gsr.gsr_group;
grp->sin_family = AF_INET;
grp->sin_addr.s_addr = streamAddr;
grp->sin_port = 0;
src = (struct sockaddr_in*)&gsr.gsr_source;
src->sin_family = AF_INET;
src->sin_addr.s_addr = sourceAddr;
src->sin_port = 0;
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, MCAST_LEAVE_SOURCE_GROUP, &gsr, sizeof(gsr)) < 0, "setsockopt(MCAST_LEAVE_SOURCE_GROUP)", return false);
}
else {
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = streamAddr;
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
ERROR_IF_RET(setsockopt(socketDesc, SOL_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0, "setsockopt(IP_DROP_MEMBERSHIP)", return false);
}
// Update multicasting flag // Update multicasting flag
isActive = false; isActive = false;
} }
@@ -154,72 +230,77 @@ int cIptvUdpSocket::Read(unsigned char* BufferAddr, unsigned int BufferLen)
error("Invalid socket in %s\n", __FUNCTION__); error("Invalid socket in %s\n", __FUNCTION__);
return -1; return -1;
} }
socklen_t addrlen = sizeof(sockAddr);
int len = 0; int len = 0;
struct msghdr msgh; // Read data from socket in a loop
struct cmsghdr *cmsg; do {
struct iovec iov; socklen_t addrlen = sizeof(sockAddr);
char cbuf[256]; struct msghdr msgh;
// Initialize iov and msgh structures struct iovec iov;
memset(&msgh, 0, sizeof(struct msghdr)); char cbuf[256];
iov.iov_base = BufferAddr; len = 0;
iov.iov_len = BufferLen; // Initialize iov and msgh structures
msgh.msg_control = cbuf; memset(&msgh, 0, sizeof(struct msghdr));
msgh.msg_controllen = sizeof(cbuf); iov.iov_base = BufferAddr;
msgh.msg_name = &sockAddr; iov.iov_len = BufferLen;
msgh.msg_namelen = addrlen; msgh.msg_control = cbuf;
msgh.msg_iov = &iov; msgh.msg_controllen = sizeof(cbuf);
msgh.msg_iovlen = 1; msgh.msg_name = &sockAddr;
msgh.msg_flags = 0; msgh.msg_namelen = addrlen;
// Read data from socket msgh.msg_iov = &iov;
if (isActive && socketDesc && BufferAddr && (BufferLen > 0)) msgh.msg_iovlen = 1;
len = (int)recvmsg(socketDesc, &msgh, MSG_DONTWAIT); msgh.msg_flags = 0;
if (len < 0) {
ERROR_IF(errno != EAGAIN, "recvmsg()"); if (isActive && socketDesc && BufferAddr && (BufferLen > 0))
return -1; len = (int)recvmsg(socketDesc, &msgh, MSG_DONTWAIT);
} else
else if (len > 0) { break;
// Process auxiliary received data and validate source address if (len > 0) {
for (cmsg = CMSG_FIRSTHDR(&msgh); (sourceAddr != INADDR_ANY) && (cmsg != NULL); cmsg = CMSG_NXTHDR(&msgh, cmsg)) { #ifndef __FreeBSD__
if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) { // Process auxiliary received data and validate source address
struct in_pktinfo *i = (struct in_pktinfo *)CMSG_DATA(cmsg); for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
if (i->ipi_addr.s_addr != sourceAddr) { if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) {
//debug("Discard packet due to invalid source address: %s", inet_ntoa(i->ipi_addr)); struct in_pktinfo *i = (struct in_pktinfo *)CMSG_DATA(cmsg);
return 0; if ((i->ipi_addr.s_addr == streamAddr) || (htonl(INADDR_ANY) == streamAddr)) {
} #endif // __FreeBSD__
} if (BufferAddr[0] == TS_SYNC_BYTE)
} return len;
if (BufferAddr[0] == TS_SYNC_BYTE) else if (len > 3) {
return len; // http://www.networksorcery.com/enp/rfc/rfc2250.txt
else if (len > 3) { // version
// http://www.networksorcery.com/enp/rfc/rfc2250.txt unsigned int v = (BufferAddr[0] >> 6) & 0x03;
// version // extension bit
unsigned int v = (BufferAddr[0] >> 6) & 0x03; unsigned int x = (BufferAddr[0] >> 4) & 0x01;
// extension bit // cscr count
unsigned int x = (BufferAddr[0] >> 4) & 0x01; unsigned int cc = BufferAddr[0] & 0x0F;
// cscr count // payload type: MPEG2 TS = 33
unsigned int cc = BufferAddr[0] & 0x0F; //unsigned int pt = readBuffer[1] & 0x7F;
// payload type: MPEG2 TS = 33 // header lenght
//unsigned int pt = readBuffer[1] & 0x7F; unsigned int headerlen = (3 + cc) * (unsigned int)sizeof(uint32_t);
// header lenght // check if extension
unsigned int headerlen = (3 + cc) * (unsigned int)sizeof(uint32_t); if (x) {
// check if extension // extension header length
if (x) { unsigned int ehl = (((BufferAddr[headerlen + 2] & 0xFF) << 8) |
// extension header length (BufferAddr[headerlen + 3] & 0xFF));
unsigned int ehl = (((BufferAddr[headerlen + 2] & 0xFF) << 8) | // update header length
(BufferAddr[headerlen + 3] & 0xFF)); headerlen += (ehl + 1) * (unsigned int)sizeof(uint32_t);
// update header length }
headerlen += (ehl + 1) * (unsigned int)sizeof(uint32_t); // Check that rtp is version 2 and payload contains multiple of TS packet data
if ((v == 2) && (((len - headerlen) % TS_SIZE) == 0) &&
(BufferAddr[headerlen] == TS_SYNC_BYTE)) {
// Set argument point to payload in read buffer
memmove(BufferAddr, &BufferAddr[headerlen], (len - headerlen));
return (len - headerlen);
}
}
#ifndef __FreeBSD__
}
}
} }
// Check that rtp is version 2 and payload contains multiple of TS packet data #endif // __FreeBSD__
if ((v == 2) && (((len - headerlen) % TS_SIZE) == 0) && }
(BufferAddr[headerlen] == TS_SYNC_BYTE)) { } while (len > 0);
// Set argument point to payload in read buffer ERROR_IF_RET(len < 0 && errno != EAGAIN, "recvmsg()", return -1);
memmove(BufferAddr, &BufferAddr[headerlen], (len - headerlen));
return (len - headerlen);
}
}
}
return 0; return 0;
} }
@@ -237,26 +318,8 @@ cIptvTcpSocket::~cIptvTcpSocket()
bool cIptvTcpSocket::OpenSocket(const int Port, const char *StreamAddr) bool cIptvTcpSocket::OpenSocket(const int Port, const char *StreamAddr)
{ {
debug("cIptvTcpSocket::OpenSocket()\n"); debug("cIptvTcpSocket::OpenSocket()\n");
// Socket must be opened before setting the host address
// First try only the IP address return (cIptvSocket::OpenSocket(Port, false) && CheckAddress(StreamAddr, &sockAddr.sin_addr.s_addr));
sockAddr.sin_addr.s_addr = inet_addr(StreamAddr);
if (sockAddr.sin_addr.s_addr == INADDR_NONE) {
debug("Cannot convert %s directly to internet address\n", StreamAddr);
// It may be a host name, get the name
struct hostent *host;
host = gethostbyname(StreamAddr);
if (!host) {
char tmp[64];
error("gethostbyname() failed: %s is not valid address: %s", StreamAddr, strerror_r(h_errno, tmp, sizeof(tmp)));
return false;
}
sockAddr.sin_addr.s_addr = inet_addr(*host->h_addr_list);
}
return cIptvSocket::OpenSocket(Port, false);
} }
void cIptvTcpSocket::CloseSocket(void) void cIptvTcpSocket::CloseSocket(void)

View File

@@ -9,17 +9,23 @@
#define __IPTV_SOCKET_H #define __IPTV_SOCKET_H
#include <arpa/inet.h> #include <arpa/inet.h>
#ifdef __FreeBSD__
#include <netinet/in.h>
#endif // __FreeBSD__
class cIptvSocket { class cIptvSocket {
private:
int socketPort;
protected: protected:
int socketDesc; int socketDesc;
int socketPort;
struct sockaddr_in sockAddr; struct sockaddr_in sockAddr;
bool isActive; bool isActive;
protected: protected:
bool OpenSocket(const int Port, const bool isUdp); bool OpenSocket(const int Port, const bool isUdp);
void CloseSocket(void); void CloseSocket(void);
bool CheckAddress(const char *Addr, in_addr_t *InAddr);
public: public:
cIptvSocket(); cIptvSocket();
@@ -28,16 +34,19 @@ public:
class cIptvUdpSocket : public cIptvSocket { class cIptvUdpSocket : public cIptvSocket {
private: private:
in_addr_t streamAddr;
in_addr_t sourceAddr; in_addr_t sourceAddr;
bool useIGMPv3;
public: public:
cIptvUdpSocket(); cIptvUdpSocket();
virtual ~cIptvUdpSocket(); virtual ~cIptvUdpSocket();
virtual int Read(unsigned char* BufferAddr, unsigned int BufferLen); virtual int Read(unsigned char* BufferAddr, unsigned int BufferLen);
bool OpenSocket(const int Port, const in_addr_t SourceAddr = INADDR_ANY); bool OpenSocket(const int Port);
bool OpenSocket(const int Port, const char *StreamAddr, const char *SourceAddr, bool UseIGMPv3);
void CloseSocket(void); void CloseSocket(void);
bool JoinMulticast(const in_addr_t StreamAddr); bool JoinMulticast(void);
bool DropMulticast(const in_addr_t StreamAddr); bool DropMulticast(void);
}; };
class cIptvTcpSocket : public cIptvSocket { class cIptvTcpSocket : public cIptvSocket {

View File

@@ -148,7 +148,7 @@ cString cIptvStreamerStatistics::GetStreamerStatistic()
long bitrate = elapsed ? (long)(1000.0L * dataBytes / KILOBYTE(1) / elapsed) : 0L; long bitrate = elapsed ? (long)(1000.0L * dataBytes / KILOBYTE(1) / elapsed) : 0L;
if (!IptvConfig.GetUseBytes()) if (!IptvConfig.GetUseBytes())
bitrate *= 8; bitrate *= 8;
cString info = cString::sprintf("Stream bitrate: %ld k%s/s\n", bitrate, IptvConfig.GetUseBytes() ? "B" : "bit"); cString info = cString::sprintf("%ld k%s/s", bitrate, IptvConfig.GetUseBytes() ? "B" : "bit");
dataBytes = 0; dataBytes = 0;
return info; return info;
} }

View File

@@ -108,8 +108,8 @@ bool cIptvStreamer::Set(const char* Location, const int Parameter, const int Ind
cString cIptvStreamer::GetInformation(void) cString cIptvStreamer::GetInformation(void)
{ {
//debug("cIptvStreamer::GetInformation()"); //debug("cIptvStreamer::GetInformation()");
cString info("Stream:"); cString info;
if (protocol) if (protocol)
info = cString::sprintf("%s %s", *info, *protocol->GetInformation()); info = protocol->GetInformation();
return cString::sprintf("%s\n", *info); return info;
} }