Snapshot 2009-06-11
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
* $Id: component.c,v 1.3 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
* $Id: component.c,v 1.4 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/component.h"
|
||||
#include "server/connection.h"
|
||||
|
||||
cServerComponent::cServerComponent(const char *Protocol, const char *ListenIp,
|
||||
uint ListenPort):
|
||||
uint ListenPort, int Type, int IpProto):
|
||||
m_Protocol(Protocol),
|
||||
m_Listen(Type, IpProto),
|
||||
m_ListenIp(ListenIp),
|
||||
m_ListenPort(ListenPort)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: component.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
* $Id: component.h,v 1.3 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_COMPONENT_H
|
||||
@@ -17,8 +17,8 @@ class cServerConnection;
|
||||
|
||||
class cServerComponent: public cListObject {
|
||||
private:
|
||||
cTBSocket m_Listen;
|
||||
const char *m_Protocol;
|
||||
cTBSocket m_Listen;
|
||||
const char *m_ListenIp;
|
||||
uint m_ListenPort;
|
||||
|
||||
@@ -27,7 +27,7 @@ protected:
|
||||
virtual cServerConnection *NewClient(void) = 0;
|
||||
|
||||
public:
|
||||
cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort);
|
||||
cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort, int Type = SOCK_STREAM, int IpProto = 0);
|
||||
virtual ~cServerComponent();
|
||||
|
||||
/* Starts listening on the specified Port, override if you want to do things
|
||||
|
||||
447
server/componentIGMP.c
Normal file
447
server/componentIGMP.c
Normal file
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
* $Id: componentIGMP.c,v 1.1 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/igmp.h>
|
||||
|
||||
#include "server/componentIGMP.h"
|
||||
#include "server/connectionIGMP.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
#ifndef IGMP_ALL_HOSTS
|
||||
#define IGMP_ALL_HOSTS htonl(0xE0000001L)
|
||||
#endif
|
||||
#ifndef IGMP_ALL_ROUTER
|
||||
#define IGMP_ALL_ROUTER htonl(0xE0000002L)
|
||||
#endif
|
||||
|
||||
// IGMP parameters according to RFC2236. All time values in seconds.
|
||||
#define IGMP_ROBUSTNESS 2
|
||||
#define IGMP_QUERY_INTERVAL 125
|
||||
#define IGMP_QUERY_RESPONSE_INTERVAL 10
|
||||
#define IGMP_GROUP_MEMBERSHIP_INTERVAL (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL)
|
||||
#define IGMP_OTHER_QUERIER_PRESENT_INTERVAL (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL / 2)
|
||||
#define IGMP_STARTUP_QUERY_INTERVAL (IGMP_QUERY_INTERVAL / 4)
|
||||
#define IGMP_STARTUP_QUERY_COUNT IGMP_ROBUSTNESS
|
||||
// This value is 1/10 sec. RFC default is 10. Reduced to minimum to free unused channels ASAP
|
||||
#define IGMP_LAST_MEMBER_QUERY_INTERVAL_TS 1
|
||||
#define IGMP_LAST_MEMBER_QUERY_COUNT IGMP_ROBUSTNESS
|
||||
|
||||
// operations on struct timeval
|
||||
#define TV_CMP(a, cmp, b) (a.tv_sec == b.tv_sec ? a.tv_usec cmp b.tv_usec : a.tv_sec cmp b.tv_sec)
|
||||
#define TV_SET(tv) (tv.tv_sec || tv.tv_usec)
|
||||
#define TV_CLR(tv) memset(&tv, 0, sizeof(tv))
|
||||
#define TV_CPY(dst, src) memcpy(&dst, &src, sizeof(dst))
|
||||
#define TV_ADD(dst, ts) dst.tv_sec += ts / 10; dst.tv_usec += (ts % 10) * 100000; if (dst.tv_usec >= 1000000) { dst.tv_usec -= 1000000; dst.tv_sec++; }
|
||||
|
||||
class cMulticastGroup: public cListObject
|
||||
{
|
||||
public:
|
||||
cConnectionIGMP *connection;
|
||||
in_addr_t group;
|
||||
in_addr_t reporter;
|
||||
struct timeval timeout;
|
||||
struct timeval v1timer;
|
||||
struct timeval retransmit;
|
||||
|
||||
cMulticastGroup(in_addr_t Group);
|
||||
};
|
||||
|
||||
cMulticastGroup::cMulticastGroup(in_addr_t Group) :
|
||||
connection(NULL),
|
||||
group(Group),
|
||||
reporter(0)
|
||||
{
|
||||
TV_CLR(timeout);
|
||||
TV_CLR(v1timer);
|
||||
TV_CLR(retransmit);
|
||||
}
|
||||
|
||||
void logIGMP(uint8_t type, struct in_addr Src, struct in_addr Dst, struct in_addr Grp)
|
||||
{
|
||||
const char* msg;
|
||||
switch (type) {
|
||||
case IGMP_MEMBERSHIP_QUERY: msg = "membership query"; break;
|
||||
case IGMP_V1_MEMBERSHIP_REPORT: msg = "V1 membership report"; break;
|
||||
case IGMP_V2_MEMBERSHIP_REPORT: msg = "V2 membership report"; break;
|
||||
case IGMP_V2_LEAVE_GROUP: msg = "leave group"; break;
|
||||
default: msg = "unknown"; break;
|
||||
}
|
||||
char* s = strdup(inet_ntoa(Src));
|
||||
char* d = strdup(inet_ntoa(Dst));
|
||||
dsyslog("streamdev-server IGMP: Received %s from %s (dst %s) for %s", msg, s, d, inet_ntoa(Grp));
|
||||
free(s);
|
||||
free(d);
|
||||
}
|
||||
|
||||
/* Taken from http://tools.ietf.org/html/rfc1071 */
|
||||
uint16_t inetChecksum(uint16_t *addr, int count)
|
||||
{
|
||||
uint32_t sum = 0;
|
||||
while (count > 1) {
|
||||
sum += *addr++;
|
||||
count -= 2;
|
||||
}
|
||||
|
||||
if( count > 0 )
|
||||
sum += * (uint8_t *) addr;
|
||||
|
||||
while (sum>>16)
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
|
||||
return ~sum;
|
||||
}
|
||||
|
||||
cComponentIGMP::cComponentIGMP(void):
|
||||
cServerComponent("IGMP", "0.0.0.0", 0, SOCK_RAW, IPPROTO_IGMP),
|
||||
cThread("IGMP timeout handler"),
|
||||
m_BindIp(inet_addr(StreamdevServerSetup.IGMPBindIP)),
|
||||
m_MaxChannelNumber(0),
|
||||
m_StartupQueryCount(IGMP_STARTUP_QUERY_COUNT),
|
||||
m_Querier(true)
|
||||
{
|
||||
}
|
||||
|
||||
cComponentIGMP::~cComponentIGMP(void)
|
||||
{
|
||||
}
|
||||
|
||||
cMulticastGroup* cComponentIGMP::FindGroup(in_addr_t Group) const
|
||||
{
|
||||
cMulticastGroup *group = m_Groups.First();
|
||||
while (group && group->group != Group)
|
||||
group = m_Groups.Next(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
bool cComponentIGMP::Initialize(void)
|
||||
{
|
||||
if (cServerComponent::Initialize() && IGMPMembership(IGMP_ALL_ROUTER))
|
||||
{
|
||||
for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
|
||||
{
|
||||
if (channel->GroupSep())
|
||||
continue;
|
||||
int num = channel->Number();
|
||||
if (!IGMPMembership(htonl(MULTICAST_PRIV_MIN + num)))
|
||||
break;
|
||||
m_MaxChannelNumber = num;
|
||||
}
|
||||
if (m_MaxChannelNumber == 0)
|
||||
{
|
||||
IGMPMembership(IGMP_ALL_ROUTER, false);
|
||||
esyslog("streamdev-server IGMP: no multicast group joined");
|
||||
}
|
||||
else
|
||||
{
|
||||
Start();
|
||||
}
|
||||
}
|
||||
return m_MaxChannelNumber > 0;
|
||||
}
|
||||
|
||||
void cComponentIGMP::Destruct(void)
|
||||
{
|
||||
if (m_MaxChannelNumber > 0)
|
||||
{
|
||||
Cancel(3);
|
||||
for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
|
||||
{
|
||||
if (channel->GroupSep())
|
||||
continue;
|
||||
int num = channel->Number();
|
||||
if (num > m_MaxChannelNumber)
|
||||
break;
|
||||
IGMPMembership(htonl(MULTICAST_PRIV_MIN + num), false);
|
||||
}
|
||||
IGMPMembership(IGMP_ALL_ROUTER, false);
|
||||
}
|
||||
m_MaxChannelNumber = 0;
|
||||
cServerComponent::Destruct();
|
||||
}
|
||||
|
||||
cServerConnection *cComponentIGMP::NewClient(void)
|
||||
{
|
||||
return new cConnectionIGMP("IGMP", StreamdevServerSetup.IGMPClientPort, (eStreamType) StreamdevServerSetup.IGMPStreamType);
|
||||
}
|
||||
|
||||
cServerConnection* cComponentIGMP::Accept(void)
|
||||
{
|
||||
ssize_t recv_len;
|
||||
int ip_hdrlen, ip_datalen;
|
||||
struct ip *ip;
|
||||
struct igmp *igmp;
|
||||
|
||||
while ((recv_len = ::recvfrom(Socket(), m_ReadBuffer, sizeof(m_ReadBuffer), 0, NULL, NULL)) < 0 && errno == EINTR)
|
||||
errno = 0;
|
||||
|
||||
if (recv_len < 0) {
|
||||
esyslog("streamdev-server IGMP: read failed: %m");
|
||||
return NULL;
|
||||
}
|
||||
else if (recv_len < (ssize_t) sizeof(struct ip)) {
|
||||
esyslog("streamdev-server IGMP: IP packet too short");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ip = (struct ip*) m_ReadBuffer;
|
||||
|
||||
// filter out my own packets
|
||||
if (ip->ip_src.s_addr == m_BindIp)
|
||||
return NULL;
|
||||
|
||||
ip_hdrlen = ip->ip_hl << 2;
|
||||
#ifdef __FreeBSD__
|
||||
ip_datalen = ip->ip_len;
|
||||
#else
|
||||
ip_datalen = ntohs(ip->ip_len) - ip_hdrlen;
|
||||
#endif
|
||||
if (ip->ip_p != IPPROTO_IGMP) {
|
||||
esyslog("streamdev-server IGMP: Unexpected protocol %hhu", ip->ip_p);
|
||||
return NULL;
|
||||
}
|
||||
if (recv_len < ip_hdrlen + IGMP_MINLEN) {
|
||||
esyslog("streamdev-server IGMP: packet too short");
|
||||
return NULL;
|
||||
}
|
||||
igmp = (struct igmp*) (m_ReadBuffer + ip_hdrlen);
|
||||
uint16_t chksum = igmp->igmp_cksum;
|
||||
igmp->igmp_cksum = 0;
|
||||
if (chksum != inetChecksum((uint16_t *)igmp, ip_datalen))
|
||||
{
|
||||
esyslog("INVALID CHECKSUM %d %d %d %d 0x%x 0x%x", ntohs(ip->ip_len), ip_hdrlen, ip_datalen, recv_len, chksum, inetChecksum((uint16_t *)igmp, ip_datalen));
|
||||
return NULL;
|
||||
}
|
||||
logIGMP(igmp->igmp_type, ip->ip_src, ip->ip_dst, igmp->igmp_group);
|
||||
return ProcessMessage(igmp, igmp->igmp_group.s_addr, ip->ip_src.s_addr);
|
||||
}
|
||||
|
||||
cServerConnection* cComponentIGMP::ProcessMessage(struct igmp *Igmp, in_addr_t Group, in_addr_t Sender)
|
||||
{
|
||||
cServerConnection* conn = NULL;
|
||||
cMulticastGroup* group;
|
||||
LOCK_THREAD;
|
||||
switch (Igmp->igmp_type) {
|
||||
case IGMP_MEMBERSHIP_QUERY:
|
||||
if (ntohl(Sender) < ntohl(m_BindIp))
|
||||
IGMPStartOtherQuerierPresentTimer();
|
||||
break;
|
||||
case IGMP_V1_MEMBERSHIP_REPORT:
|
||||
case IGMP_V2_MEMBERSHIP_REPORT:
|
||||
group = FindGroup(Group);
|
||||
if (!group) {
|
||||
group = new cMulticastGroup(Group);
|
||||
m_Groups.Add(group);
|
||||
}
|
||||
if (!group->connection) {
|
||||
IGMPStartMulticast(group);
|
||||
conn = group->connection;
|
||||
}
|
||||
IGMPStartTimer(group, Sender);
|
||||
if (Igmp->igmp_type == IGMP_V1_MEMBERSHIP_REPORT)
|
||||
IGMPStartV1HostTimer(group);
|
||||
break;
|
||||
case IGMP_V2_LEAVE_GROUP:
|
||||
group = FindGroup(Group);
|
||||
if (group && !TV_SET(group->v1timer)) {
|
||||
if (group->reporter == Sender) {
|
||||
IGMPStartTimerAfterLeave(group, m_Querier ? IGMP_LAST_MEMBER_QUERY_INTERVAL_TS : Igmp->igmp_code);
|
||||
if (m_Querier)
|
||||
IGMPSendGroupQuery(group);
|
||||
IGMPStartRetransmitTimer(group);
|
||||
}
|
||||
m_CondWait.Signal();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
void cComponentIGMP::Action()
|
||||
{
|
||||
while (Running()) {
|
||||
struct timeval now;
|
||||
struct timeval next;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
TV_CPY(next, now);
|
||||
next.tv_sec += IGMP_QUERY_INTERVAL;
|
||||
|
||||
cMulticastGroup *del = NULL;
|
||||
{
|
||||
LOCK_THREAD;
|
||||
if (TV_CMP(m_GeneralQueryTimer, <, now)) {
|
||||
dsyslog("General Query");
|
||||
IGMPSendGeneralQuery();
|
||||
IGMPStartGeneralQueryTimer();
|
||||
}
|
||||
if (TV_CMP(next, >, m_GeneralQueryTimer))
|
||||
TV_CPY(next, m_GeneralQueryTimer);
|
||||
|
||||
for (cMulticastGroup *group = m_Groups.First(); group; group = m_Groups.Next(group)) {
|
||||
if (TV_CMP(group->timeout, <, now)) {
|
||||
IGMPStopMulticast(group);
|
||||
IGMPClearRetransmitTimer(group);
|
||||
if (del)
|
||||
m_Groups.Del(del);
|
||||
del = group;
|
||||
}
|
||||
else if (m_Querier && TV_SET(group->retransmit) && TV_CMP(group->retransmit, <, now)) {
|
||||
IGMPSendGroupQuery(group);
|
||||
IGMPStartRetransmitTimer(group);
|
||||
if (TV_CMP(next, >, group->retransmit))
|
||||
TV_CPY(next, group->retransmit);
|
||||
}
|
||||
else if (TV_SET(group->v1timer) && TV_CMP(group->v1timer, <, now)) {
|
||||
TV_CLR(group->v1timer);
|
||||
}
|
||||
else {
|
||||
if (TV_CMP(next, >, group->timeout))
|
||||
TV_CPY(next, group->timeout);
|
||||
if (TV_SET(group->retransmit) && TV_CMP(next, >, group->retransmit))
|
||||
TV_CPY(next, group->retransmit);
|
||||
if (TV_SET(group->v1timer) && TV_CMP(next, >, group->v1timer))
|
||||
TV_CPY(next, group->v1timer);
|
||||
}
|
||||
}
|
||||
if (del)
|
||||
m_Groups.Del(del);
|
||||
}
|
||||
|
||||
int sleep = (next.tv_sec - now.tv_sec) * 1000;
|
||||
sleep += (next.tv_usec - now.tv_usec) / 1000;
|
||||
if (next.tv_usec < now.tv_usec)
|
||||
sleep += 1000;
|
||||
dsyslog("Sleeping %d ms", sleep);
|
||||
m_CondWait.Wait(sleep);
|
||||
}
|
||||
}
|
||||
|
||||
bool cComponentIGMP::IGMPMembership(in_addr_t Group, bool Add)
|
||||
{
|
||||
struct ip_mreqn mreq;
|
||||
mreq.imr_multiaddr.s_addr = Group;
|
||||
mreq.imr_address.s_addr = INADDR_ANY;
|
||||
mreq.imr_ifindex = 0;
|
||||
if (setsockopt(Socket(), IPPROTO_IP, Add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
|
||||
{
|
||||
esyslog("streamdev-server IGMP: unable to %s %s: %m", Add ? "join" : "leave", inet_ntoa(mreq.imr_multiaddr));
|
||||
if (errno == ENOBUFS)
|
||||
esyslog("consider increasing sys.net.ipv4.igmp_max_memberships");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPSendQuery(in_addr_t Group, int Timeout)
|
||||
{
|
||||
struct sockaddr_in dst;
|
||||
struct igmp query;
|
||||
|
||||
dst.sin_family = AF_INET;
|
||||
dst.sin_port = IPPROTO_IGMP;
|
||||
dst.sin_addr.s_addr = Group;
|
||||
query.igmp_type = IGMP_MEMBERSHIP_QUERY;
|
||||
query.igmp_code = Timeout * 10;
|
||||
query.igmp_cksum = 0;
|
||||
query.igmp_group.s_addr = (Group == IGMP_ALL_HOSTS) ? 0 : Group;
|
||||
query.igmp_cksum = inetChecksum((uint16_t *) &query, sizeof(query));
|
||||
|
||||
for (int i = 0; i < 5 && ::sendto(Socket(), &query, sizeof(query), 0, (sockaddr*)&dst, sizeof(dst)) == -1; i++) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
esyslog("streamdev-server IGMP: unable to query group %s: %m", inet_ntoa(dst.sin_addr));
|
||||
break;
|
||||
}
|
||||
cCondWait::SleepMs(10);
|
||||
}
|
||||
}
|
||||
|
||||
// Querier state actions
|
||||
void cComponentIGMP::IGMPStartGeneralQueryTimer()
|
||||
{
|
||||
m_Querier = true;
|
||||
if (m_StartupQueryCount) {
|
||||
gettimeofday(&m_GeneralQueryTimer, NULL);
|
||||
m_GeneralQueryTimer.tv_sec += IGMP_STARTUP_QUERY_INTERVAL;
|
||||
m_StartupQueryCount--;
|
||||
}
|
||||
else {
|
||||
gettimeofday(&m_GeneralQueryTimer, NULL);
|
||||
m_GeneralQueryTimer.tv_sec += IGMP_QUERY_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartOtherQuerierPresentTimer()
|
||||
{
|
||||
m_Querier = false;
|
||||
m_StartupQueryCount = 0;
|
||||
gettimeofday(&m_GeneralQueryTimer, NULL);
|
||||
m_GeneralQueryTimer.tv_sec += IGMP_OTHER_QUERIER_PRESENT_INTERVAL;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPSendGeneralQuery()
|
||||
{
|
||||
IGMPSendQuery(IGMP_ALL_HOSTS, IGMP_QUERY_RESPONSE_INTERVAL);
|
||||
}
|
||||
|
||||
// Group state actions
|
||||
void cComponentIGMP::IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member)
|
||||
{
|
||||
gettimeofday(&Group->timeout, NULL);
|
||||
Group->timeout.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL;
|
||||
TV_CLR(Group->retransmit);
|
||||
Group->reporter = Member;
|
||||
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartV1HostTimer(cMulticastGroup* Group)
|
||||
{
|
||||
gettimeofday(&Group->v1timer, NULL);
|
||||
Group->v1timer.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartTimerAfterLeave(cMulticastGroup* Group, unsigned int MaxResponseTimeTs)
|
||||
{
|
||||
//Group->Update(time(NULL) + MaxResponseTime * IGMP_LAST_MEMBER_QUERY_COUNT / 10);
|
||||
MaxResponseTimeTs *= IGMP_LAST_MEMBER_QUERY_COUNT;
|
||||
gettimeofday(&Group->timeout, NULL);
|
||||
TV_ADD(Group->timeout, MaxResponseTimeTs);
|
||||
TV_CLR(Group->retransmit);
|
||||
Group->reporter = 0;
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartRetransmitTimer(cMulticastGroup* Group)
|
||||
{
|
||||
gettimeofday(&Group->retransmit, NULL);
|
||||
TV_ADD(Group->retransmit, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPClearRetransmitTimer(cMulticastGroup* Group)
|
||||
{
|
||||
TV_CLR(Group->retransmit);
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPSendGroupQuery(cMulticastGroup* Group)
|
||||
{
|
||||
IGMPSendQuery(Group->group, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStartMulticast(cMulticastGroup* Group)
|
||||
{
|
||||
in_addr_t g = ntohl(Group->group);
|
||||
if (g > MULTICAST_PRIV_MIN && g <= MULTICAST_PRIV_MAX) {
|
||||
cChannel *channel = Channels.GetByNumber(g - MULTICAST_PRIV_MIN);
|
||||
Group->connection = (cConnectionIGMP*) NewClient();
|
||||
if (!Group->connection->Start(channel, Group->group)) {
|
||||
DELETENULL(Group->connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cComponentIGMP::IGMPStopMulticast(cMulticastGroup* Group)
|
||||
{
|
||||
if (Group->connection)
|
||||
Group->connection->Stop();
|
||||
}
|
||||
62
server/componentIGMP.h
Normal file
62
server/componentIGMP.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* $Id: componentIGMP.h,v 1.1 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_IGMPSERVER_H
|
||||
#define VDR_STREAMDEV_IGMPSERVER_H
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <vdr/thread.h>
|
||||
#include "server/component.h"
|
||||
|
||||
class cConnectionIGMP;
|
||||
class cMulticastGroup;
|
||||
|
||||
class cComponentIGMP: public cServerComponent, public cThread {
|
||||
private:
|
||||
char m_ReadBuffer[2048];
|
||||
cList<cMulticastGroup> m_Groups;
|
||||
in_addr_t m_BindIp;
|
||||
int m_MaxChannelNumber;
|
||||
struct timeval m_GeneralQueryTimer;
|
||||
int m_StartupQueryCount;
|
||||
bool m_Querier;
|
||||
cCondWait m_CondWait;
|
||||
|
||||
cMulticastGroup* FindGroup(in_addr_t Group) const;
|
||||
|
||||
/* Add or remove local host to multicast group */
|
||||
bool IGMPMembership(in_addr_t Group, bool Add = true);
|
||||
void IGMPSendQuery(in_addr_t Group, int Timeout);
|
||||
|
||||
cServerConnection* ProcessMessage(struct igmp *Igmp, in_addr_t Group, in_addr_t Sender);
|
||||
|
||||
void IGMPStartGeneralQueryTimer();
|
||||
void IGMPStartOtherQuerierPresentTimer();
|
||||
void IGMPSendGeneralQuery();
|
||||
|
||||
void IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member);
|
||||
void IGMPStartV1HostTimer(cMulticastGroup* Group);
|
||||
void IGMPStartTimerAfterLeave(cMulticastGroup* Group, unsigned int MaxResponseTime);
|
||||
void IGMPStartRetransmitTimer(cMulticastGroup* Group);
|
||||
void IGMPClearRetransmitTimer(cMulticastGroup* Group);
|
||||
void IGMPSendGroupQuery(cMulticastGroup* Group);
|
||||
void IGMPStartMulticast(cMulticastGroup* Group);
|
||||
void IGMPStopMulticast(cMulticastGroup* Group);
|
||||
|
||||
virtual void Action();
|
||||
|
||||
protected:
|
||||
virtual cServerConnection *NewClient(void);
|
||||
|
||||
public:
|
||||
virtual bool Initialize(void);
|
||||
virtual void Destruct(void);
|
||||
virtual cServerConnection* Accept(void);
|
||||
|
||||
cComponentIGMP(void);
|
||||
~cComponentIGMP(void);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_IGMPSERVER_H
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: connection.c,v 1.10 2007/05/07 12:25:11 schmirl Exp $
|
||||
* $Id: connection.c,v 1.12 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/connection.h"
|
||||
@@ -12,7 +12,8 @@
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
|
||||
cServerConnection::cServerConnection(const char *Protocol):
|
||||
cServerConnection::cServerConnection(const char *Protocol, int Type):
|
||||
cTBSocket(Type),
|
||||
m_Protocol(Protocol),
|
||||
m_DeferClose(false),
|
||||
m_Pending(false),
|
||||
@@ -139,11 +140,7 @@ cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority)
|
||||
Dprintf(" * GetDevice(const cChannel*, int)\n");
|
||||
Dprintf(" * -------------------------------\n");
|
||||
|
||||
#if VDRVERSNUM < 10500
|
||||
device = cDevice::GetDevice(Channel, Priority);
|
||||
#else
|
||||
device = cDevice::GetDevice(Channel, Priority, false);
|
||||
#endif
|
||||
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
@@ -161,11 +158,7 @@ cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority)
|
||||
const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel());
|
||||
isyslog("streamdev-server: Detaching current receiver");
|
||||
Detach();
|
||||
#if VDRVERSNUM < 10500
|
||||
device = cDevice::GetDevice(Channel, Priority);
|
||||
#else
|
||||
device = cDevice::GetDevice(Channel, Priority, false);
|
||||
#endif
|
||||
Attach();
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: connection.h,v 1.5 2007/04/16 11:01:02 schmirl Exp $
|
||||
* $Id: connection.h,v 1.7 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVER_CONNECTION_H
|
||||
@@ -44,9 +44,12 @@ protected:
|
||||
public:
|
||||
/* If you derive, specify a short string such as HTTP for Protocol, which
|
||||
will be displayed in error messages */
|
||||
cServerConnection(const char *Protocol);
|
||||
cServerConnection(const char *Protocol, int Type = SOCK_STREAM);
|
||||
virtual ~cServerConnection();
|
||||
|
||||
/* If true, any client IP will be accepted */
|
||||
virtual bool CanAuthenticate(void) { return false; }
|
||||
|
||||
/* Gets called if the client has been accepted by the core */
|
||||
virtual void Welcome(void) { }
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/*
|
||||
* $Id: connectionHTTP.c,v 1.13 2008/03/28 15:11:40 schmirl Exp $
|
||||
* $Id: connectionHTTP.c,v 1.16 2009/02/13 07:02:19 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "server/connectionHTTP.h"
|
||||
#include "server/menuHTTP.h"
|
||||
#include "server/server.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
cConnectionHTTP::cConnectionHTTP(void):
|
||||
@@ -26,6 +27,11 @@ cConnectionHTTP::~cConnectionHTTP()
|
||||
delete m_LiveStreamer;
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::CanAuthenticate(void)
|
||||
{
|
||||
return opt_auth != NULL;
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::Command(char *Cmd)
|
||||
{
|
||||
Dprintf("command %s\n", Cmd);
|
||||
@@ -44,10 +50,22 @@ bool cConnectionHTTP::Command(char *Cmd)
|
||||
if (strncasecmp(Cmd, "Host:", 5) == 0) {
|
||||
Dprintf("Host-Header\n");
|
||||
m_Host = (std::string) skipspace(Cmd + 5);
|
||||
return true;
|
||||
}
|
||||
else if (strncasecmp(Cmd, "Authorization:", 14) == 0) {
|
||||
Cmd = skipspace(Cmd + 14);
|
||||
if (strncasecmp(Cmd, "Basic", 5) == 0) {
|
||||
Dprintf("'Authorization Basic'-Header\n");
|
||||
m_Authorization = (std::string) skipspace(Cmd + 5);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Dprintf("header\n");
|
||||
return true;
|
||||
default:
|
||||
// skip additional blank lines
|
||||
if (*Cmd == '\0')
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
return false; // ??? shouldn't happen
|
||||
@@ -56,6 +74,16 @@ bool cConnectionHTTP::Command(char *Cmd)
|
||||
bool cConnectionHTTP::ProcessRequest(void)
|
||||
{
|
||||
Dprintf("process\n");
|
||||
if (!StreamdevHosts.Acceptable(RemoteIpAddr()))
|
||||
{
|
||||
if (!opt_auth || m_Authorization.empty() || m_Authorization.compare(opt_auth) != 0) {
|
||||
isyslog("streamdev-server: HTTP authorization required");
|
||||
DeferClose();
|
||||
return Respond("HTTP/1.0 401 Authorization Required")
|
||||
&& Respond("WWW-authenticate: basic Realm=\"Streamdev-Server\")")
|
||||
&& Respond("");
|
||||
}
|
||||
}
|
||||
if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
|
||||
switch (m_Job) {
|
||||
case hjListing:
|
||||
@@ -183,8 +211,10 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts)
|
||||
const char* pType = type.c_str();
|
||||
if (strcasecmp(pType, "PS") == 0) {
|
||||
m_StreamType = stPS;
|
||||
#if APIVERSNUM < 10703
|
||||
} else if (strcasecmp(pType, "PES") == 0) {
|
||||
m_StreamType = stPES;
|
||||
#endif
|
||||
} else if (strcasecmp(pType, "TS") == 0) {
|
||||
m_StreamType = stTS;
|
||||
} else if (strcasecmp(pType, "ES") == 0) {
|
||||
@@ -236,7 +266,9 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts)
|
||||
{
|
||||
case stTS: base += "TS/"; break;
|
||||
case stPS: base += "PS/"; break;
|
||||
#if APIVERSNUM < 10703
|
||||
case stPES: base += "PES/"; break;
|
||||
#endif
|
||||
case stES: base += "ES/"; break;
|
||||
case stExtern: base += "Extern/"; break;
|
||||
default: break;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: connectionHTTP.h,v 1.5 2008/03/28 15:11:40 schmirl Exp $
|
||||
* $Id: connectionHTTP.h,v 1.6 2008/10/14 11:05:48 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
|
||||
@@ -30,6 +30,7 @@ private:
|
||||
|
||||
std::string m_Request;
|
||||
std::string m_Host;
|
||||
std::string m_Authorization;
|
||||
//std::map<std::string,std::string> m_Headers; TODO: later?
|
||||
eHTTPStatus m_Status;
|
||||
eHTTPJob m_Job;
|
||||
@@ -52,6 +53,8 @@ public:
|
||||
virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
|
||||
virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
|
||||
|
||||
virtual bool CanAuthenticate(void);
|
||||
|
||||
virtual bool Command(char *Cmd);
|
||||
bool CmdGET(const std::string &Opts);
|
||||
|
||||
|
||||
64
server/connectionIGMP.c
Normal file
64
server/connectionIGMP.c
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* $Id: connectionIGMP.c,v 1.1 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "server/connectionIGMP.h"
|
||||
#include "server/server.h"
|
||||
#include "server/setup.h"
|
||||
#include <vdr/channels.h>
|
||||
|
||||
cConnectionIGMP::cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType) :
|
||||
cServerConnection(Name, SOCK_DGRAM),
|
||||
m_LiveStreamer(NULL),
|
||||
m_ClientPort(ClientPort),
|
||||
m_StreamType(StreamType)
|
||||
{
|
||||
}
|
||||
|
||||
cConnectionIGMP::~cConnectionIGMP()
|
||||
{
|
||||
delete m_LiveStreamer;
|
||||
}
|
||||
|
||||
bool cConnectionIGMP::Start(cChannel *Channel, in_addr_t Dst)
|
||||
{
|
||||
if (Channel != NULL) {
|
||||
cDevice *device = GetDevice(Channel, 0);
|
||||
if (device != NULL) {
|
||||
device->SwitchChannel(Channel, false);
|
||||
struct in_addr ip;
|
||||
ip.s_addr = Dst;
|
||||
if (Connect(inet_ntoa(ip), m_ClientPort)) {
|
||||
m_LiveStreamer = new cStreamdevLiveStreamer(0);
|
||||
if (m_LiveStreamer->SetChannel(Channel, m_StreamType)) {
|
||||
m_LiveStreamer->SetDevice(device);
|
||||
if (!SetDSCP())
|
||||
LOG_ERROR_STR("unable to set DSCP sockopt");
|
||||
Dprintf("streamer start\n");
|
||||
m_LiveStreamer->Start(this);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
esyslog("streamdev-server IGMP: SetDevice failed");
|
||||
DELETENULL(m_LiveStreamer);
|
||||
}
|
||||
else
|
||||
esyslog("streamdev-server IGMP: Connect failed: %m");
|
||||
}
|
||||
else
|
||||
esyslog("streamdev-server IGMP: GetDevice failed");
|
||||
}
|
||||
else
|
||||
esyslog("streamdev-server IGMP: Channel not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
void cConnectionIGMP::Stop()
|
||||
{
|
||||
if (m_LiveStreamer) {
|
||||
m_LiveStreamer->Stop();
|
||||
DELETENULL(m_LiveStreamer);
|
||||
}
|
||||
}
|
||||
45
server/connectionIGMP.h
Normal file
45
server/connectionIGMP.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* $Id: connectionIGMP.h,v 1.1 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H
|
||||
#define VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H
|
||||
|
||||
#include "connection.h"
|
||||
#include "server/livestreamer.h"
|
||||
|
||||
#include <tools/select.h>
|
||||
|
||||
#define MULTICAST_PRIV_MIN ((uint32_t) 0xefff0000)
|
||||
#define MULTICAST_PRIV_MAX ((uint32_t) 0xeffffeff)
|
||||
|
||||
class cStreamdevLiveStreamer;
|
||||
|
||||
class cConnectionIGMP: public cServerConnection {
|
||||
private:
|
||||
cStreamdevLiveStreamer *m_LiveStreamer;
|
||||
int m_ClientPort;
|
||||
eStreamType m_StreamType;
|
||||
|
||||
public:
|
||||
cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType);
|
||||
virtual ~cConnectionIGMP();
|
||||
|
||||
bool Start(cChannel *Channel, in_addr_t Dst);
|
||||
void Stop();
|
||||
|
||||
/* Not used here */
|
||||
virtual bool Command(char *Cmd) { return false; }
|
||||
|
||||
virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
|
||||
virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
|
||||
|
||||
virtual bool Abort(void) const;
|
||||
};
|
||||
|
||||
inline bool cConnectionIGMP::Abort(void) const
|
||||
{
|
||||
return !m_LiveStreamer || m_LiveStreamer->Abort();
|
||||
}
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: connectionVTP.c,v 1.17 2008/03/13 16:01:18 schmirl Exp $
|
||||
* $Id: connectionVTP.c,v 1.19 2009/01/16 11:35:44 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/connectionVTP.h"
|
||||
@@ -595,17 +595,18 @@ bool cConnectionVTP::CmdCAPS(char *Opts)
|
||||
return Respond(220, "Capability \"%s\" accepted", Opts);
|
||||
}
|
||||
|
||||
#if APIVERSNUM < 10703
|
||||
if (strcasecmp(Opts, "PES") == 0) {
|
||||
m_StreamType = stPES;
|
||||
return Respond(220, "Capability \"%s\" accepted", Opts);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (strcasecmp(Opts, "EXTERN") == 0) {
|
||||
m_StreamType = stExtern;
|
||||
return Respond(220, "Capability \"%s\" accepted", Opts);
|
||||
}
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
//
|
||||
// Deliver section filters data in separate, channel-independent data stream
|
||||
//
|
||||
@@ -613,7 +614,6 @@ bool cConnectionVTP::CmdCAPS(char *Opts)
|
||||
m_FiltersSupport = true;
|
||||
return Respond(220, "Capability \"%s\" accepted", Opts);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Respond(561, "Capability \"%s\" not known", Opts);
|
||||
}
|
||||
@@ -648,13 +648,8 @@ bool cConnectionVTP::CmdPORT(char *Opts)
|
||||
if (ep == Opts || !isspace(*ep))
|
||||
return Respond(500, "Use: PORT Id Destination");
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
if (id != siLive && id != siLiveFilter)
|
||||
return Respond(501, "Wrong connection id %d", id);
|
||||
#else
|
||||
if (id != siLive)
|
||||
return Respond(501, "Wrong connection id %d", id);
|
||||
#endif
|
||||
|
||||
Opts = skipspace(ep);
|
||||
n = 0;
|
||||
@@ -681,7 +676,6 @@ bool cConnectionVTP::CmdPORT(char *Opts)
|
||||
|
||||
isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport);
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
if (id == siLiveFilter) {
|
||||
m_FiltersSupport = true;
|
||||
if(m_FilterStreamer)
|
||||
@@ -703,7 +697,6 @@ bool cConnectionVTP::CmdPORT(char *Opts)
|
||||
|
||||
return Respond(220, "Port command ok, data connection opened");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(m_LiveSocket && m_LiveStreamer)
|
||||
m_LiveStreamer->Stop();
|
||||
@@ -746,14 +739,12 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
|
||||
if(m_LiveSocket)
|
||||
m_LiveStreamer->Start(m_LiveSocket);
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
if(m_FiltersSupport) {
|
||||
if(!m_FilterStreamer)
|
||||
m_FilterStreamer = new cStreamdevFilterStreamer;
|
||||
m_FilterStreamer->SetDevice(dev);
|
||||
//m_FilterStreamer->SetChannel(chan);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Respond(220, "Channel tuned");
|
||||
}
|
||||
@@ -788,7 +779,6 @@ bool cConnectionVTP::CmdDELP(char *Opts)
|
||||
|
||||
bool cConnectionVTP::CmdADDF(char *Opts)
|
||||
{
|
||||
#if VDRVERSNUM >= 10300
|
||||
int pid, tid, mask;
|
||||
char *ep;
|
||||
|
||||
@@ -810,14 +800,10 @@ bool cConnectionVTP::CmdADDF(char *Opts)
|
||||
return m_FilterStreamer->SetFilter(pid, tid, mask, true)
|
||||
? Respond(220, "Filter %d transferring", pid)
|
||||
: Respond(560, "Filter %d not available", pid);
|
||||
#else
|
||||
return Respond(500, "ADDF known but unimplemented with VDR < 1.3.0");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdDELF(char *Opts)
|
||||
{
|
||||
#if VDRVERSNUM >= 10307
|
||||
int pid, tid, mask;
|
||||
char *ep;
|
||||
|
||||
@@ -838,9 +824,6 @@ bool cConnectionVTP::CmdDELF(char *Opts)
|
||||
|
||||
m_FilterStreamer->SetFilter(pid, tid, mask, false);
|
||||
return Respond(220, "Filter %d stopped", pid);
|
||||
#else
|
||||
return Respond(500, "DELF known but unimplemented with VDR < 1.3.0");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdABRT(char *Opts)
|
||||
@@ -857,12 +840,10 @@ bool cConnectionVTP::CmdABRT(char *Opts)
|
||||
DELETENULL(m_LiveStreamer);
|
||||
DELETENULL(m_LiveSocket);
|
||||
break;
|
||||
#if VDRVERSNUM >= 10300
|
||||
case siLiveFilter:
|
||||
DELETENULL(m_FilterStreamer);
|
||||
DELETENULL(m_FilterSocket);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return Respond(501, "Wrong connection id %d", id);
|
||||
break;
|
||||
|
||||
@@ -12,9 +12,9 @@ class cLSTTHandler;
|
||||
|
||||
class cConnectionVTP: public cServerConnection {
|
||||
friend class cLSTEHandler;
|
||||
// if your compiler doesn't understand the following statement
|
||||
// (e.g. gcc 2.x), simply remove it and try again ;-)
|
||||
#if !defined __GNUC__ || __GNUC__ >= 3
|
||||
using cServerConnection::Respond;
|
||||
#endif
|
||||
|
||||
private:
|
||||
cTBSocket *m_LiveSocket;
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
/*
|
||||
* $Id: livefilter.c,v 1.4 2007/04/24 11:06:12 schmirl Exp $
|
||||
* $Id: livefilter.c,v 1.7 2009/02/13 13:02:40 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/livefilter.h"
|
||||
#include "server/streamer.h"
|
||||
#include "common.h"
|
||||
|
||||
#ifndef TS_SIZE
|
||||
# define TS_SIZE 188
|
||||
#endif
|
||||
#ifndef TS_SYNC_BYTE
|
||||
# define TS_SYNC_BYTE 0x47
|
||||
#endif
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
|
||||
cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevStreamer *Streamer) {
|
||||
m_Streamer = Streamer;
|
||||
}
|
||||
@@ -31,6 +26,7 @@ void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data,
|
||||
buffer[1] = ((Pid >> 8) & 0x3f) | (pos==0 ? 0x40 : 0); /* bit 6: payload unit start indicator (PUSI) */
|
||||
buffer[2] = Pid & 0xff;
|
||||
buffer[3] = Tid;
|
||||
// this makes it a proprietary stream
|
||||
buffer[4] = (uchar)chunk;
|
||||
memcpy(buffer + 5, Data + pos, chunk);
|
||||
length -= chunk;
|
||||
@@ -41,5 +37,3 @@ void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data,
|
||||
m_Streamer->ReportOverflow(TS_SIZE - p);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // VDRVERSNUM >= 10300
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: livefilter.h,v 1.4 2007/04/24 11:29:29 schmirl Exp $
|
||||
* $Id: livefilter.h,v 1.5 2008/04/07 14:27:31 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMEV_LIVEFILTER_H
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
#include <vdr/config.h>
|
||||
|
||||
# if VDRVERSNUM >= 10300
|
||||
|
||||
#include <vdr/filter.h>
|
||||
|
||||
class cStreamdevStreamer;
|
||||
@@ -31,5 +29,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
# endif // VDRVERSNUM >= 10300
|
||||
#endif // VDR_STREAMEV_LIVEFILTER_H
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
#include <libsi/section.h>
|
||||
#include <libsi/descriptor.h>
|
||||
|
||||
#include "remux/ts2ps.h"
|
||||
#include "remux/ts2es.h"
|
||||
#include "remux/extern.h"
|
||||
|
||||
#include <vdr/ringbuffer.h>
|
||||
|
||||
#include "server/livestreamer.h"
|
||||
#include "server/livefilter.h"
|
||||
#include "remux/ts2ps.h"
|
||||
#include "remux/ts2es.h"
|
||||
#include "remux/extern.h"
|
||||
#include "common.h"
|
||||
|
||||
#define TSPATREPACKER
|
||||
@@ -27,23 +28,13 @@ protected:
|
||||
virtual void Receive(uchar *Data, int Length);
|
||||
|
||||
public:
|
||||
#if VDRVERSNUM < 10500
|
||||
cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, int Ca, int Priority, const int *Pids);
|
||||
#else
|
||||
cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, tChannelID ChannelID, int Priority, const int *Pids);
|
||||
#endif
|
||||
virtual ~cStreamdevLiveReceiver();
|
||||
};
|
||||
|
||||
#if VDRVERSNUM < 10500
|
||||
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, int Ca,
|
||||
int Priority, const int *Pids):
|
||||
cReceiver(Ca, Priority, 0, Pids),
|
||||
#else
|
||||
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, tChannelID ChannelID,
|
||||
int Priority, const int *Pids):
|
||||
cReceiver(ChannelID, Priority, 0, Pids),
|
||||
#endif
|
||||
m_Streamer(Streamer)
|
||||
{
|
||||
}
|
||||
@@ -86,7 +77,7 @@ public:
|
||||
|
||||
cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel)
|
||||
{
|
||||
Dprintf("cStreamdevPatFilter(\"%s\")", Channel->Name());
|
||||
Dprintf("cStreamdevPatFilter(\"%s\")\n", Channel->Name());
|
||||
assert(Streamer);
|
||||
m_Channel = Channel;
|
||||
m_Streamer = Streamer;
|
||||
@@ -145,7 +136,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
case 0x10: // ISO/IEC 14496-2 Visual (MPEG-4)
|
||||
case 0x11: // ISO/IEC 14496-3 Audio with LATM transport syntax
|
||||
case 0x1b: // ISO/IEC 14496-10 Video (MPEG-4 part 10/AVC, aka H.264)
|
||||
Dprintf("cStreamdevPatFilter PMT scanner adding PID %d (%s)",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner adding PID %d (%s)\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()]);
|
||||
return stream.getPid();
|
||||
case 0x05: // ISO/IEC 13818-1 private sections
|
||||
@@ -153,19 +144,19 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) {
|
||||
switch (d->getDescriptorTag()) {
|
||||
case SI::AC3DescriptorTag:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()], "AC3");
|
||||
return stream.getPid();
|
||||
case SI::TeletextDescriptorTag:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()], "Teletext");
|
||||
return stream.getPid();
|
||||
case SI::SubtitlingDescriptorTag:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()], "DVBSUB");
|
||||
return stream.getPid();
|
||||
default:
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()], "UNKNOWN");
|
||||
break;
|
||||
}
|
||||
@@ -210,7 +201,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
return stream.getPid();
|
||||
}
|
||||
}
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s",
|
||||
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n",
|
||||
stream.getPid(), psStreamTypes[stream.getStreamType()<0x1c?stream.getStreamType():0], "UNKNOWN");
|
||||
break;
|
||||
}
|
||||
@@ -220,7 +211,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
|
||||
void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
|
||||
{
|
||||
if (Pid == 0x00) {
|
||||
if (Tid == 0x00 && !pmtPid) {
|
||||
if (Tid == 0x00) {
|
||||
SI::PAT pat(Data, false);
|
||||
if (!pat.CheckCRCAndParse())
|
||||
return;
|
||||
@@ -229,8 +220,9 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
if (!assoc.isNITPid()) {
|
||||
const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId());
|
||||
if (Channel && (Channel == m_Channel)) {
|
||||
int prevPmtPid = pmtPid;
|
||||
if (0 != (pmtPid = assoc.getPid())) {
|
||||
Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d", Channel->Name(), pmtPid);
|
||||
Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d\n", Channel->Name(), pmtPid);
|
||||
pmtSid = assoc.getServiceId();
|
||||
if (Length < TS_SIZE-5) {
|
||||
// repack PAT to TS frame and send to client
|
||||
@@ -242,25 +234,27 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
int ts_id;
|
||||
unsigned int crc, i, len;
|
||||
uint8_t *tmp, tspat_buf[TS_SIZE];
|
||||
static uint8_t ccounter = 0;
|
||||
ccounter = (ccounter + 1) % 16;
|
||||
memset(tspat_buf, 0xff, TS_SIZE);
|
||||
memset(tspat_buf, 0x0, 4 + 12 + 5); // TS_HDR_LEN + PAT_TABLE_LEN + 5
|
||||
ts_id = Channel->Tid(); // Get transport stream id of the channel
|
||||
tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h)
|
||||
tspat_buf[1] = 0x40; // Set payload unit start indicator bit
|
||||
tspat_buf[2] = 0x0; // PID
|
||||
tspat_buf[3] = 0x10; // Set payload flag to indicate precence of payload data
|
||||
tspat_buf[4] = 0x0; // PSI
|
||||
tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter
|
||||
tspat_buf[4] = 0x0; // SI pointer field
|
||||
tspat_buf[5] = 0x0; // PAT table id
|
||||
tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set
|
||||
tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1
|
||||
tspat_buf[8] = (ts_id >> 8) & 0xff; // Transport stream ID (bits 8-15)
|
||||
tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15)
|
||||
tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7)
|
||||
tspat_buf[10] = 0x01; // Version number 0, Current next indicator bit set
|
||||
tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) |
|
||||
pat.getCurrentNextIndicator();// Version number, Current next indicator
|
||||
tspat_buf[11] = 0x0; // Section number
|
||||
tspat_buf[12] = 0x0; // Last section number
|
||||
tspat_buf[13] = (pmtSid >> 8) & 0xff; // Program number (bits 8-15)
|
||||
tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15)
|
||||
tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7)
|
||||
tspat_buf[15] = (pmtPid >> 8) & 0xff; // Network ID (bits 8-12)
|
||||
tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12)
|
||||
tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7)
|
||||
crc = 0xffffffff;
|
||||
len = 12; // PAT_TABLE_LEN
|
||||
@@ -278,9 +272,11 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
#endif
|
||||
} else
|
||||
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
|
||||
m_Streamer->SetPids(pmtPid);
|
||||
Add(pmtPid, 0x02);
|
||||
pmtVersion = -1;
|
||||
if (pmtPid != prevPmtPid) {
|
||||
m_Streamer->SetPids(pmtPid);
|
||||
Add(pmtPid, 0x02);
|
||||
pmtVersion = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -295,7 +291,7 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
|
||||
return; // skip broken PMT records
|
||||
if (pmtVersion != -1) {
|
||||
if (pmtVersion != pmt.getVersionNumber()) {
|
||||
Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids");
|
||||
Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n");
|
||||
Del(pmtPid, 0x02);
|
||||
pmtPid = 0; // this triggers PAT scan
|
||||
}
|
||||
@@ -333,7 +329,9 @@ cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Paramet
|
||||
m_Device(NULL),
|
||||
m_Receiver(NULL),
|
||||
m_PatFilter(NULL),
|
||||
#if APIVERSNUM < 10703
|
||||
m_PESRemux(NULL),
|
||||
#endif
|
||||
m_ESRemux(NULL),
|
||||
m_PSRemux(NULL),
|
||||
m_ExtRemux(NULL)
|
||||
@@ -349,7 +347,9 @@ cStreamdevLiveStreamer::~cStreamdevLiveStreamer()
|
||||
DELETENULL(m_PatFilter);
|
||||
}
|
||||
DELETENULL(m_Receiver);
|
||||
#if APIVERSNUM < 10703
|
||||
delete m_PESRemux;
|
||||
#endif
|
||||
delete m_ESRemux;
|
||||
delete m_PSRemux;
|
||||
delete m_ExtRemux;
|
||||
@@ -434,11 +434,7 @@ void cStreamdevLiveStreamer::StartReceiver(void)
|
||||
DELETENULL(m_Receiver);
|
||||
if (m_NumPids > 0) {
|
||||
Dprintf("Creating Receiver to respect changed pids\n");
|
||||
#if VDRVERSNUM < 10500
|
||||
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->Ca(), m_Priority, m_Pids);
|
||||
#else
|
||||
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->GetChannelID(), m_Priority, m_Pids);
|
||||
#endif
|
||||
if (IsRunning() && m_Device != NULL) {
|
||||
Dprintf("Attaching new receiver\n");
|
||||
Attach();
|
||||
@@ -467,10 +463,12 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
|
||||
return SetPids(pid);
|
||||
}
|
||||
|
||||
#if APIVERSNUM < 10703
|
||||
case stPES:
|
||||
m_PESRemux = new cRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||
m_Channel->Spids(), false);
|
||||
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
|
||||
#endif
|
||||
|
||||
case stPS:
|
||||
m_PSRemux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||
@@ -483,6 +481,10 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
|
||||
Detach();
|
||||
DELETENULL(m_PatFilter);
|
||||
}
|
||||
// Set pids from cChannel
|
||||
SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
|
||||
if (m_Channel->Vpid() != m_Channel->Ppid())
|
||||
SetPid(m_Channel->Ppid(), true);
|
||||
// Set pids from PMT
|
||||
m_PatFilter = new cStreamdevPatFilter(this, m_Channel);
|
||||
return true;
|
||||
@@ -506,8 +508,10 @@ int cStreamdevLiveStreamer::Put(const uchar *Data, int Count)
|
||||
case stTSPIDS:
|
||||
return cStreamdevStreamer::Put(Data, Count);
|
||||
|
||||
#if APIVERSNUM < 10703
|
||||
case stPES:
|
||||
return m_PESRemux->Put(Data, Count);
|
||||
#endif
|
||||
|
||||
case stES:
|
||||
return m_ESRemux->Put(Data, Count);
|
||||
@@ -530,8 +534,10 @@ uchar *cStreamdevLiveStreamer::Get(int &Count)
|
||||
case stTSPIDS:
|
||||
return cStreamdevStreamer::Get(Count);
|
||||
|
||||
#if APIVERSNUM < 10703
|
||||
case stPES:
|
||||
return m_PESRemux->Get(Count);
|
||||
#endif
|
||||
|
||||
case stES:
|
||||
return m_ESRemux->Get(Count);
|
||||
@@ -555,9 +561,11 @@ void cStreamdevLiveStreamer::Del(int Count)
|
||||
cStreamdevStreamer::Del(Count);
|
||||
break;
|
||||
|
||||
#if APIVERSNUM < 10703
|
||||
case stPES:
|
||||
m_PESRemux->Del(Count);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case stES:
|
||||
m_ESRemux->Del(Count);
|
||||
@@ -618,7 +626,6 @@ std::string cStreamdevLiveStreamer::Report(void)
|
||||
|
||||
// --- cStreamdevFilterStreamer -------------------------------------------------
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
cStreamdevFilterStreamer::cStreamdevFilterStreamer():
|
||||
cStreamdevStreamer("streamdev-filterstreaming"),
|
||||
m_Device(NULL),
|
||||
@@ -720,5 +727,3 @@ void cStreamdevFilterStreamer::ChannelSwitch(const cDevice *Device, int ChannelN
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // if VDRVERSNUM >= 10300
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
class cTS2PSRemux;
|
||||
class cTS2ESRemux;
|
||||
class cExternRemux;
|
||||
#if APIVERSNUM < 10703
|
||||
class cRemux;
|
||||
#endif
|
||||
class cStreamdevPatFilter;
|
||||
class cStreamdevLiveReceiver;
|
||||
|
||||
@@ -27,7 +29,9 @@ private:
|
||||
cDevice *m_Device;
|
||||
cStreamdevLiveReceiver *m_Receiver;
|
||||
cStreamdevPatFilter *m_PatFilter;
|
||||
#if APIVERSNUM < 10703
|
||||
cRemux *m_PESRemux;
|
||||
#endif
|
||||
cTS2ESRemux *m_ESRemux;
|
||||
cTS2PSRemux *m_PSRemux;
|
||||
cExternRemux *m_ExtRemux;
|
||||
@@ -58,8 +62,6 @@ public:
|
||||
|
||||
// --- cStreamdevFilterStreamer -------------------------------------------------
|
||||
|
||||
# if VDRVERSNUM >= 10300
|
||||
|
||||
//#include <vdr/status.h>
|
||||
|
||||
class cStreamdevLiveFilter;
|
||||
@@ -85,6 +87,4 @@ public:
|
||||
//virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
|
||||
};
|
||||
|
||||
# endif // if VDRVERSNUM >= 10300
|
||||
|
||||
#endif // VDR_STREAMDEV_LIVESTREAMER_H
|
||||
|
||||
@@ -112,10 +112,10 @@ const cChannel* cChannelList::GetGroup(int Index)
|
||||
|
||||
// ******************** cHtmlChannelList ******************
|
||||
const char* cHtmlChannelList::menu =
|
||||
"[<a href=\"/\">Home</a> (<a href=\"all.html\">no script</a>)] "
|
||||
"[<a href=\"tree.html\">Tree View</a>] "
|
||||
"[<a href=\"groups.html\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] "
|
||||
"[<a href=\"channels.html\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] ";
|
||||
"[<a href=\"/\">Home</a> (<a href=\"all.html\" tvid=\"RED\">no script</a>)] "
|
||||
"[<a href=\"tree.html\" tvid=\"GREEN\">Tree View</a>] "
|
||||
"[<a href=\"groups.html\" tvid=\"YELLOW\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] "
|
||||
"[<a href=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] ";
|
||||
|
||||
const char* cHtmlChannelList::css =
|
||||
"<style type=\"text/css\">\n"
|
||||
@@ -201,8 +201,10 @@ std::string cHtmlChannelList::StreamTypeMenu()
|
||||
(std::string) "[<a href=\"/TS/" + self + "\">TS</a>] ");
|
||||
typeMenu += (streamType == stPS ? (std::string) "[PS] " :
|
||||
(std::string) "[<a href=\"/PS/" + self + "\">PS</a>] ");
|
||||
#if APIVERSNUM < 10703
|
||||
typeMenu += (streamType == stPES ? (std::string) "[PES] " :
|
||||
(std::string) "[<a href=\"/PES/" + self + "\">PES</a>] ");
|
||||
#endif
|
||||
typeMenu += (streamType == stES ? (std::string) "[ES] " :
|
||||
(std::string) "[<a href=\"/ES/" + self + "\">ES</a>] ");
|
||||
typeMenu += (streamType == stExtern ? (std::string) "[Extern] " :
|
||||
@@ -336,9 +338,26 @@ std::string cHtmlChannelList::GroupTitle()
|
||||
std::string cHtmlChannelList::ItemText()
|
||||
{
|
||||
std::string line;
|
||||
std::string suffix;
|
||||
|
||||
switch (streamType) {
|
||||
case stTS: suffix = (std::string) ".ts"; break;
|
||||
case stPS: suffix = (std::string) ".vob"; break;
|
||||
#if APIVERSNUM < 10703
|
||||
// for Network Media Tank
|
||||
case stPES: suffix = (std::string) ".vdr"; break;
|
||||
#endif
|
||||
default: suffix = "";
|
||||
}
|
||||
line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";
|
||||
line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + "\">" +
|
||||
current->Name() + "</a>";
|
||||
line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + suffix + "\"";
|
||||
|
||||
// for Network Media Tank
|
||||
line += (std::string) " vod ";
|
||||
if (current->Number() < 1000)
|
||||
line += (std::string) " tvid=\"" + (const char*) itoa(current->Number()) + "\"";
|
||||
|
||||
line += (std::string) ">" + current->Name() + "</a>";
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; current->Apid(i) != 0; ++i, ++count)
|
||||
@@ -351,11 +370,11 @@ std::string cHtmlChannelList::ItemText()
|
||||
int index = 1;
|
||||
for (int i = 0; current->Apid(i) != 0; ++i, ++index) {
|
||||
line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
|
||||
"+" + (const char*)itoa(index) + "\" class=\"apid\">" + current->Alang(i) + "</a>";
|
||||
"+" + (const char*)itoa(index) + suffix + "\" class=\"apid\" vod>" + current->Alang(i) + "</a>";
|
||||
}
|
||||
for (int i = 0; current->Dpid(i) != 0; ++i, ++index) {
|
||||
line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
|
||||
"+" + (const char*)itoa(index) + "\" class=\"dpid\">" + current->Dlang(i) + "</a>";
|
||||
"+" + (const char*)itoa(index) + suffix + "\" class=\"dpid\" vod>" + current->Dlang(i) + "</a>";
|
||||
}
|
||||
}
|
||||
line += "</li>";
|
||||
@@ -364,10 +383,8 @@ std::string cHtmlChannelList::ItemText()
|
||||
|
||||
// ******************** cM3uChannelList ******************
|
||||
cM3uChannelList::cM3uChannelList(cChannelIterator *Iterator, const char* Base)
|
||||
: cChannelList(Iterator)
|
||||
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
|
||||
, m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
||||
#endif
|
||||
: cChannelList(Iterator),
|
||||
m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8")
|
||||
{
|
||||
base = strdup(Base);
|
||||
m3uState = msFirst;
|
||||
@@ -398,11 +415,7 @@ std::string cM3uChannelList::Next()
|
||||
return "";
|
||||
}
|
||||
|
||||
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
|
||||
std::string name = (std::string) m_IConv.Convert(channel->Name());
|
||||
#else
|
||||
std::string name = channel->Name();
|
||||
#endif
|
||||
|
||||
if (channel->GroupSep())
|
||||
{
|
||||
|
||||
@@ -126,9 +126,7 @@ class cM3uChannelList: public cChannelList
|
||||
char *base;
|
||||
enum eM3uState { msFirst, msContinue, msLast };
|
||||
eM3uState m3uState;
|
||||
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
|
||||
cCharSetConv m_IConv;
|
||||
#endif
|
||||
public:
|
||||
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: audio/x-mpegurl\r\n"; };
|
||||
virtual bool HasNext();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/*
|
||||
* $Id: server.c,v 1.5 2007/04/02 10:32:34 schmirl Exp $
|
||||
* $Id: server.c,v 1.10 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/server.h"
|
||||
#include "server/componentVTP.h"
|
||||
#include "server/componentHTTP.h"
|
||||
#include "server/componentIGMP.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
#include <vdr/tools.h>
|
||||
@@ -13,14 +14,15 @@
|
||||
#include <errno.h>
|
||||
|
||||
cSVDRPhosts StreamdevHosts;
|
||||
char *opt_auth = NULL;
|
||||
char *opt_remux = NULL;
|
||||
|
||||
cStreamdevServer *cStreamdevServer::m_Instance = NULL;
|
||||
cList<cServerComponent> cStreamdevServer::m_Servers;
|
||||
cList<cServerConnection> cStreamdevServer::m_Clients;
|
||||
|
||||
cStreamdevServer::cStreamdevServer(void):
|
||||
cThread("streamdev server"),
|
||||
m_Active(false)
|
||||
cThread("streamdev server")
|
||||
{
|
||||
Start();
|
||||
}
|
||||
@@ -35,6 +37,12 @@ void cStreamdevServer::Initialize(void)
|
||||
if (m_Instance == NULL) {
|
||||
if (StreamdevServerSetup.StartVTPServer) Register(new cComponentVTP);
|
||||
if (StreamdevServerSetup.StartHTTPServer) Register(new cComponentHTTP);
|
||||
if (StreamdevServerSetup.StartIGMPServer) {
|
||||
if (strcmp(StreamdevServerSetup.IGMPBindIP, "0.0.0.0") == 0)
|
||||
esyslog("streamdev-server: Not starting IGMP. IGMP must be bound to a local IP");
|
||||
else
|
||||
Register(new cComponentIGMP);
|
||||
}
|
||||
|
||||
m_Instance = new cStreamdevServer;
|
||||
}
|
||||
@@ -47,10 +55,8 @@ void cStreamdevServer::Destruct(void)
|
||||
|
||||
void cStreamdevServer::Stop(void)
|
||||
{
|
||||
if (m_Active) {
|
||||
m_Active = false;
|
||||
if (Running())
|
||||
Cancel(3);
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevServer::Register(cServerComponent *Server)
|
||||
@@ -60,8 +66,6 @@ void cStreamdevServer::Register(cServerComponent *Server)
|
||||
|
||||
void cStreamdevServer::Action(void)
|
||||
{
|
||||
m_Active = true;
|
||||
|
||||
/* Initialize Server components, deleting those that failed */
|
||||
for (cServerComponent *c = m_Servers.First(); c;) {
|
||||
cServerComponent *next = m_Servers.Next(c);
|
||||
@@ -72,11 +76,11 @@ void cStreamdevServer::Action(void)
|
||||
|
||||
if (m_Servers.Count() == 0) {
|
||||
esyslog("ERROR: no streamdev server activated, exiting");
|
||||
m_Active = false;
|
||||
Cancel(-1);
|
||||
}
|
||||
|
||||
cTBSelect select;
|
||||
while (m_Active) {
|
||||
while (Running()) {
|
||||
select.Clear();
|
||||
|
||||
/* Ask all Server components to register to the selector */
|
||||
@@ -102,9 +106,9 @@ void cStreamdevServer::Action(void)
|
||||
sel = 0;
|
||||
}
|
||||
}
|
||||
} while (sel < 0 && errno == ETIMEDOUT && m_Active);
|
||||
} while (sel < 0 && errno == ETIMEDOUT && Running());
|
||||
|
||||
if (!m_Active)
|
||||
if (!Running())
|
||||
break;
|
||||
if (sel < 0) {
|
||||
esyslog("fatal error, server exiting: %m");
|
||||
@@ -115,13 +119,15 @@ void cStreamdevServer::Action(void)
|
||||
for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c)){
|
||||
if (sel && select.CanRead(c->Socket())) {
|
||||
cServerConnection *client = c->Accept();
|
||||
if (!client)
|
||||
continue;
|
||||
m_Clients.Add(client);
|
||||
|
||||
if (m_Clients.Count() > StreamdevServerSetup.MaxClients) {
|
||||
esyslog("streamdev: too many clients, rejecting %s:%d",
|
||||
client->RemoteIp().c_str(), client->RemotePort());
|
||||
client->Reject();
|
||||
} else if (!StreamdevHosts.Acceptable(client->RemoteIpAddr())) {
|
||||
} else if (!client->CanAuthenticate() && !StreamdevHosts.Acceptable(client->RemoteIpAddr())) {
|
||||
esyslog("streamdev: client %s:%d not allowed to connect",
|
||||
client->RemoteIp().c_str(), client->RemotePort());
|
||||
client->Reject();
|
||||
@@ -164,6 +170,4 @@ void cStreamdevServer::Action(void)
|
||||
c->Destruct();
|
||||
m_Servers.Del(c);
|
||||
}
|
||||
|
||||
m_Active = false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: server.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
* $Id: server.h,v 1.6 2008/10/22 11:59:32 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVER_H
|
||||
@@ -10,12 +10,14 @@
|
||||
#include "server/component.h"
|
||||
#include "server/connection.h"
|
||||
|
||||
#define STREAMDEVHOSTSPATH (*AddDirectory(cPlugin::ConfigDirectory(), "streamdevhosts.conf"))
|
||||
#define DEFAULT_EXTERNREMUX (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "externremux.sh"))
|
||||
#define STREAMDEVHOSTSPATH (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "streamdevhosts.conf"))
|
||||
|
||||
extern char *opt_auth;
|
||||
extern char *opt_remux;
|
||||
|
||||
class cStreamdevServer: public cThread {
|
||||
private:
|
||||
bool m_Active;
|
||||
|
||||
static cStreamdevServer *m_Instance;
|
||||
static cList<cServerComponent> m_Servers;
|
||||
static cList<cServerConnection> m_Clients;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/*
|
||||
* $Id: setup.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
* $Id: setup.c,v 1.6 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/menuitems.h>
|
||||
|
||||
#include "server/setup.h"
|
||||
#include "server/server.h"
|
||||
#include "i18n.h"
|
||||
|
||||
cStreamdevServerSetup StreamdevServerSetup;
|
||||
|
||||
@@ -16,11 +15,15 @@ cStreamdevServerSetup::cStreamdevServerSetup(void) {
|
||||
VTPServerPort = 2004;
|
||||
StartHTTPServer = true;
|
||||
HTTPServerPort = 3000;
|
||||
HTTPStreamType = stPES;
|
||||
SuspendMode = smOffer;
|
||||
HTTPStreamType = stTS;
|
||||
StartIGMPServer = false;
|
||||
IGMPClientPort = 1234;
|
||||
IGMPStreamType = stTS;
|
||||
SuspendMode = smAlways;
|
||||
AllowSuspend = false;
|
||||
strcpy(VTPBindIP, "0.0.0.0");
|
||||
strcpy(HTTPBindIP, "0.0.0.0");
|
||||
strcpy(IGMPBindIP, "0.0.0.0");
|
||||
}
|
||||
|
||||
bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) {
|
||||
@@ -32,6 +35,10 @@ bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) {
|
||||
else if (strcmp(Name, "HTTPServerPort") == 0) HTTPServerPort = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPStreamType") == 0) HTTPStreamType = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPBindIP") == 0) strcpy(HTTPBindIP, Value);
|
||||
else if (strcmp(Name, "StartIGMPServer") == 0) StartIGMPServer = atoi(Value);
|
||||
else if (strcmp(Name, "IGMPClientPort") == 0) IGMPClientPort = atoi(Value);
|
||||
else if (strcmp(Name, "IGMPStreamType") == 0) IGMPStreamType = atoi(Value);
|
||||
else if (strcmp(Name, "IGMPBindIP") == 0) strcpy(IGMPBindIP, Value);
|
||||
else if (strcmp(Name, "SuspendMode") == 0) SuspendMode = atoi(Value);
|
||||
else if (strcmp(Name, "AllowSuspend") == 0) AllowSuspend = atoi(Value);
|
||||
else return false;
|
||||
@@ -56,7 +63,11 @@ cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) {
|
||||
AddShortEdit(tr("HTTP Server Port"), m_NewSetup.HTTPServerPort);
|
||||
AddTypeEdit (tr("HTTP Streamtype"), m_NewSetup.HTTPStreamType);
|
||||
AddIpEdit (tr("Bind to IP"), m_NewSetup.HTTPBindIP);
|
||||
|
||||
AddCategory (tr("Multicast Streaming Server"));
|
||||
AddBoolEdit (tr("Start IGMP Server"), m_NewSetup.StartIGMPServer);
|
||||
AddShortEdit(tr("Multicast Client Port"), m_NewSetup.IGMPClientPort);
|
||||
AddTypeEdit (tr("Multicast Streamtype"), m_NewSetup.IGMPStreamType);
|
||||
AddIpEdit (tr("Bind to IP"), m_NewSetup.IGMPBindIP);
|
||||
SetCurrent(Get(1));
|
||||
}
|
||||
|
||||
@@ -70,7 +81,10 @@ void cStreamdevServerMenuSetupPage::Store(void) {
|
||||
|| strcmp(m_NewSetup.VTPBindIP, StreamdevServerSetup.VTPBindIP) != 0
|
||||
|| m_NewSetup.StartHTTPServer != StreamdevServerSetup.StartHTTPServer
|
||||
|| m_NewSetup.HTTPServerPort != StreamdevServerSetup.HTTPServerPort
|
||||
|| strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0) {
|
||||
|| strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0
|
||||
|| m_NewSetup.StartIGMPServer != StreamdevServerSetup.StartIGMPServer
|
||||
|| m_NewSetup.IGMPClientPort != StreamdevServerSetup.IGMPClientPort
|
||||
|| strcmp(m_NewSetup.IGMPBindIP, StreamdevServerSetup.IGMPBindIP) != 0) {
|
||||
restart = true;
|
||||
cStreamdevServer::Destruct();
|
||||
}
|
||||
@@ -83,6 +97,10 @@ void cStreamdevServerMenuSetupPage::Store(void) {
|
||||
SetupStore("HTTPServerPort", m_NewSetup.HTTPServerPort);
|
||||
SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType);
|
||||
SetupStore("HTTPBindIP", m_NewSetup.HTTPBindIP);
|
||||
SetupStore("StartIGMPServer", m_NewSetup.StartIGMPServer);
|
||||
SetupStore("IGMPClientPort", m_NewSetup.IGMPClientPort);
|
||||
SetupStore("IGMPStreamType", m_NewSetup.IGMPStreamType);
|
||||
SetupStore("IGMPBindIP", m_NewSetup.IGMPBindIP);
|
||||
SetupStore("SuspendMode", m_NewSetup.SuspendMode);
|
||||
SetupStore("AllowSuspend", m_NewSetup.AllowSuspend);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: setup.h,v 1.1.1.1 2004/12/30 22:44:21 lordjaxom Exp $
|
||||
* $Id: setup.h,v 1.2 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SETUPSERVER_H
|
||||
@@ -20,6 +20,10 @@ struct cStreamdevServerSetup {
|
||||
int HTTPServerPort;
|
||||
int HTTPStreamType;
|
||||
char HTTPBindIP[20];
|
||||
int StartIGMPServer;
|
||||
int IGMPClientPort;
|
||||
int IGMPStreamType;
|
||||
char IGMPBindIP[20];
|
||||
int SuspendMode;
|
||||
int AllowSuspend;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: streamer.c,v 1.16 2007/09/21 11:45:53 schmirl Exp $
|
||||
* $Id: streamer.c,v 1.18 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/ringbuffer.h>
|
||||
@@ -20,16 +20,15 @@ cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket,
|
||||
cStreamdevStreamer *Streamer):
|
||||
cThread("streamdev-writer"),
|
||||
m_Streamer(Streamer),
|
||||
m_Socket(Socket),
|
||||
m_Active(false)
|
||||
m_Socket(Socket)
|
||||
{
|
||||
}
|
||||
|
||||
cStreamdevWriter::~cStreamdevWriter()
|
||||
{
|
||||
Dprintf("destructing writer\n");
|
||||
m_Active = false;
|
||||
Cancel(3);
|
||||
if (Running())
|
||||
Cancel(3);
|
||||
}
|
||||
|
||||
void cStreamdevWriter::Action(void)
|
||||
@@ -39,11 +38,10 @@ void cStreamdevWriter::Action(void)
|
||||
int max = 0;
|
||||
uchar *block = NULL;
|
||||
int count, offset = 0;
|
||||
m_Active = true;
|
||||
|
||||
sel.Clear();
|
||||
sel.Add(*m_Socket, true);
|
||||
while (m_Active) {
|
||||
while (Running()) {
|
||||
if (block == NULL) {
|
||||
block = m_Streamer->Get(count);
|
||||
offset = 0;
|
||||
@@ -57,23 +55,39 @@ void cStreamdevWriter::Action(void)
|
||||
|
||||
if (sel.CanWrite(*m_Socket)) {
|
||||
int written;
|
||||
if ((written = m_Socket->Write(block + offset, count)) == -1) {
|
||||
esyslog("ERROR: streamdev-server: couldn't send data: %m");
|
||||
int pkgsize = count;
|
||||
// SOCK_DGRAM indicates multicast
|
||||
if (m_Socket->Type() == SOCK_DGRAM) {
|
||||
// don't fragment multicast packets
|
||||
// max. payload on standard local ethernet is 1416 to 1456 bytes
|
||||
// and some STBs expect complete TS packets
|
||||
// so let's always limit to 7 * TS_SIZE = 1316
|
||||
if (pkgsize > 7 * TS_SIZE)
|
||||
pkgsize = 7 * TS_SIZE;
|
||||
else
|
||||
pkgsize -= pkgsize % TS_SIZE;
|
||||
}
|
||||
if ((written = m_Socket->Write(block + offset, pkgsize)) == -1) {
|
||||
esyslog("ERROR: streamdev-server: couldn't send %d bytes: %m", pkgsize);
|
||||
break;
|
||||
}
|
||||
|
||||
// statistics
|
||||
if (count > max)
|
||||
max = count;
|
||||
|
||||
offset += written;
|
||||
count -= written;
|
||||
if (count == 0) {
|
||||
|
||||
// less than one TS packet left:
|
||||
// delete what we've written so far and get next chunk
|
||||
if (count < TS_SIZE) {
|
||||
m_Streamer->Del(offset);
|
||||
block = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_Active = false;
|
||||
Dprintf("Max. Transmit Blocksize was: %d\n", max);
|
||||
}
|
||||
|
||||
@@ -81,7 +95,6 @@ void cStreamdevWriter::Action(void)
|
||||
|
||||
cStreamdevStreamer::cStreamdevStreamer(const char *Name):
|
||||
cThread(Name),
|
||||
m_Active(false),
|
||||
m_Running(false),
|
||||
m_Writer(NULL),
|
||||
m_RingBuffer(new cRingBufferLinear(STREAMERBUFSIZE, TS_SIZE * 2,
|
||||
@@ -109,7 +122,7 @@ void cStreamdevStreamer::Start(cTBSocket *Socket)
|
||||
|
||||
void cStreamdevStreamer::Activate(bool On)
|
||||
{
|
||||
if (On && !m_Active) {
|
||||
if (On && !Active()) {
|
||||
Dprintf("activate streamer\n");
|
||||
m_Writer->Start();
|
||||
cThread::Start();
|
||||
@@ -118,9 +131,8 @@ void cStreamdevStreamer::Activate(bool On)
|
||||
|
||||
void cStreamdevStreamer::Stop(void)
|
||||
{
|
||||
if (m_Active) {
|
||||
if (Running()) {
|
||||
Dprintf("stopping streamer\n");
|
||||
m_Active = false;
|
||||
Cancel(3);
|
||||
}
|
||||
if (m_Running) {
|
||||
@@ -132,8 +144,7 @@ void cStreamdevStreamer::Stop(void)
|
||||
|
||||
void cStreamdevStreamer::Action(void)
|
||||
{
|
||||
m_Active = true;
|
||||
while (m_Active) {
|
||||
while (Running()) {
|
||||
int got;
|
||||
uchar *block = m_RingBuffer->Get(got);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: streamer.h,v 1.8 2007/04/02 10:32:34 schmirl Exp $
|
||||
* $Id: streamer.h,v 1.10 2009/02/13 10:39:22 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_STREAMER_H
|
||||
@@ -12,6 +12,10 @@
|
||||
class cTBSocket;
|
||||
class cStreamdevStreamer;
|
||||
|
||||
#ifndef TS_SIZE
|
||||
#define TS_SIZE 188
|
||||
#endif
|
||||
|
||||
#define STREAMERBUFSIZE MEGABYTE(4)
|
||||
#define WRITERBUFSIZE KILOBYTE(256)
|
||||
|
||||
@@ -21,7 +25,6 @@ class cStreamdevWriter: public cThread {
|
||||
private:
|
||||
cStreamdevStreamer *m_Streamer;
|
||||
cTBSocket *m_Socket;
|
||||
bool m_Active;
|
||||
|
||||
protected:
|
||||
virtual void Action(void);
|
||||
@@ -29,15 +32,12 @@ protected:
|
||||
public:
|
||||
cStreamdevWriter(cTBSocket *Socket, cStreamdevStreamer *Streamer);
|
||||
virtual ~cStreamdevWriter();
|
||||
|
||||
bool IsActive(void) const { return m_Active; }
|
||||
};
|
||||
|
||||
// --- cStreamdevStreamer -----------------------------------------------------
|
||||
|
||||
class cStreamdevStreamer: public cThread {
|
||||
private:
|
||||
bool m_Active;
|
||||
bool m_Running;
|
||||
cStreamdevWriter *m_Writer;
|
||||
cRingBufferLinear *m_RingBuffer;
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
|
||||
virtual void Start(cTBSocket *Socket);
|
||||
virtual void Stop(void);
|
||||
bool Abort(void) const;
|
||||
bool Abort(void);
|
||||
|
||||
void Activate(bool On);
|
||||
int Receive(uchar *Data, int Length) { return m_RingBuffer->Put(Data, Length); }
|
||||
@@ -68,9 +68,9 @@ public:
|
||||
virtual void Attach(void) {}
|
||||
};
|
||||
|
||||
inline bool cStreamdevStreamer::Abort(void) const
|
||||
inline bool cStreamdevStreamer::Abort(void)
|
||||
{
|
||||
return m_Active && !m_Writer->IsActive();
|
||||
return Active() && !m_Writer->Active();
|
||||
}
|
||||
|
||||
#endif // VDR_STREAMDEV_STREAMER_H
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: suspend.c,v 1.1.1.1 2004/12/30 22:44:21 lordjaxom Exp $
|
||||
* $Id: suspend.c,v 1.3 2008/10/22 11:59:32 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/suspend.h"
|
||||
@@ -7,13 +7,12 @@
|
||||
#include "common.h"
|
||||
|
||||
cSuspendLive::cSuspendLive(void)
|
||||
#if VDRVERSNUM >= 10300
|
||||
: cThread("Streamdev: server suspend")
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
cSuspendLive::~cSuspendLive() {
|
||||
Stop();
|
||||
Detach();
|
||||
}
|
||||
|
||||
@@ -26,26 +25,15 @@ void cSuspendLive::Activate(bool On) {
|
||||
}
|
||||
|
||||
void cSuspendLive::Stop(void) {
|
||||
if (m_Active) {
|
||||
m_Active = false;
|
||||
if (Running())
|
||||
Cancel(3);
|
||||
}
|
||||
}
|
||||
|
||||
void cSuspendLive::Action(void) {
|
||||
#if VDRVERSNUM < 10300
|
||||
isyslog("Streamdev: Suspend Live thread started (pid = %d)", getpid());
|
||||
#endif
|
||||
|
||||
m_Active = true;
|
||||
while (m_Active) {
|
||||
while (Running()) {
|
||||
DeviceStillPicture(suspend_mpg, sizeof(suspend_mpg));
|
||||
usleep(100000);
|
||||
cCondWait::SleepMs(100);
|
||||
}
|
||||
|
||||
#if VDRVERSNUM < 10300
|
||||
isyslog("Streamdev: Suspend Live thread stopped");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool cSuspendCtl::m_Active = false;
|
||||
@@ -61,7 +49,7 @@ cSuspendCtl::~cSuspendCtl() {
|
||||
}
|
||||
|
||||
eOSState cSuspendCtl::ProcessKey(eKeys Key) {
|
||||
if (!m_Suspend->IsActive() || Key == kBack) {
|
||||
if (!m_Suspend->Active() || Key == kBack) {
|
||||
DELETENULL(m_Suspend);
|
||||
return osEnd;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $Id: suspend.h,v 1.1.1.1 2004/12/30 22:44:26 lordjaxom Exp $
|
||||
* $Id: suspend.h,v 1.2 2008/10/22 11:59:32 schmirl Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SUSPEND_H
|
||||
@@ -7,10 +7,7 @@
|
||||
|
||||
#include <vdr/player.h>
|
||||
|
||||
class cSuspendLive: public cPlayer, cThread {
|
||||
private:
|
||||
bool m_Active;
|
||||
|
||||
class cSuspendLive: public cPlayer, public cThread {
|
||||
protected:
|
||||
virtual void Activate(bool On);
|
||||
virtual void Action(void);
|
||||
@@ -20,8 +17,6 @@ protected:
|
||||
public:
|
||||
cSuspendLive(void);
|
||||
virtual ~cSuspendLive();
|
||||
|
||||
bool IsActive(void) const { return m_Active; }
|
||||
};
|
||||
|
||||
class cSuspendCtl: public cControl {
|
||||
|
||||
Reference in New Issue
Block a user