Initial revision

This commit is contained in:
lordjaxom
2004-12-30 22:43:55 +00:00
commit 302fa2e672
96 changed files with 21399 additions and 0 deletions

50
server/component.c Normal file
View File

@@ -0,0 +1,50 @@
/*
* $Id: component.c,v 1.1 2004/12/30 22:44:18 lordjaxom Exp $
*/
#include "server/component.h"
#include "server/connection.h"
#include <vdr/tools.h>
#include <string.h>
#include <errno.h>
cServerComponent::cServerComponent(const char *Protocol, const char *ListenIp,
uint ListenPort) {
m_Protocol = Protocol;
m_ListenIp = ListenIp;
m_ListenPort = ListenPort;
}
cServerComponent::~cServerComponent() {
}
bool cServerComponent::Init(void) {
if (!m_Listen.Listen(m_ListenIp, m_ListenPort, 5)) {
esyslog("Streamdev: Couldn't listen (%s) %s:%d: %s", m_Protocol, m_ListenIp,
m_ListenPort, strerror(errno));
return false;
}
isyslog("Streamdev: Listening (%s) on port %d", m_Protocol, m_ListenPort);
return true;
}
void cServerComponent::Exit(void) {
m_Listen.Close();
}
cServerConnection *cServerComponent::CanAct(const cTBSelect &Select) {
if (Select.CanRead(m_Listen)) {
cServerConnection *client = NewConnection();
if (client->Accept(m_Listen)) {
isyslog("Streamdev: Accepted new client (%s) %s:%d", m_Protocol,
(const char*)client->RemoteIp(), client->RemotePort());
return client;
} else {
esyslog("Streamdev: Couldn't accept (%s): %s", m_Protocol,
strerror(errno));
delete client;
}
}
return NULL;
}

50
server/component.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* $Id: component.h,v 1.1 2004/12/30 22:44:18 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_SERVERS_COMPONENT_H
#define VDR_STREAMDEV_SERVERS_COMPONENT_H
#include "tools/socket.h"
#include "tools/select.h"
#include <vdr/tools.h>
class cServerConnection;
/* Basic TCP listen server, all functions virtual if a derivation wants to do
things different */
class cServerComponent: public cListObject {
private:
cTBSocket m_Listen;
const char *m_Protocol;
const char *m_ListenIp;
uint m_ListenPort;
public:
cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort);
virtual ~cServerComponent();
/* Starts listening on the specified Port, override if you want to do things
different */
virtual bool Init(void);
/* Stops listening, override if you want to do things different */
virtual void Exit(void);
/* Adds the listening socket to the Select object */
virtual void AddSelect(cTBSelect &Select) const { Select.Add(m_Listen); }
/* Accepts the connection on a NewConnection() object and calls the
Welcome() on it, override if you want to do things different */
virtual cServerConnection *CanAct(const cTBSelect &Select);
/* Returns a new connection object for CanAct */
virtual cServerConnection *NewConnection(void) const = 0;
};
class cServerComponents: public cList<cServerComponent> {
};
#endif // VDR_STREAMDEV_SERVERS_COMPONENT_H

15
server/componentHTTP.c Normal file
View File

@@ -0,0 +1,15 @@
/*
* $Id: componentHTTP.c,v 1.1 2004/12/30 22:44:19 lordjaxom Exp $
*/
#include "server/componentHTTP.h"
#include "server/connectionHTTP.h"
#include "server/setup.h"
cComponentHTTP::cComponentHTTP(void):
cServerComponent("HTTP", StreamdevServerSetup.HTTPBindIP,
StreamdevServerSetup.HTTPServerPort) {
}
cComponentHTTP::~cComponentHTTP() {
}

26
server/componentHTTP.h Normal file
View File

@@ -0,0 +1,26 @@
/*
* $Id: componentHTTP.h,v 1.1 2004/12/30 22:44:19 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_HTTPSERVER_H
#define VDR_STREAMDEV_HTTPSERVER_H
#include "server/component.h"
#include "server/connectionHTTP.h"
#include <tools/socket.h>
#include <tools/select.h>
class cComponentHTTP: public cServerComponent {
public:
cComponentHTTP(void);
~cComponentHTTP(void);
virtual cServerConnection *NewConnection(void) const;
};
inline cServerConnection *cComponentHTTP::NewConnection(void) const {
return new cConnectionHTTP;
}
#endif // VDR_STREAMDEV_HTTPSERVER_H

15
server/componentVTP.c Normal file
View File

@@ -0,0 +1,15 @@
/*
* $Id: componentVTP.c,v 1.1 2004/12/30 22:44:19 lordjaxom Exp $
*/
#include "server/componentVTP.h"
#include "server/connectionVTP.h"
#include "server/setup.h"
cComponentVTP::cComponentVTP(void):
cServerComponent("VTP", StreamdevServerSetup.VTPBindIP,
StreamdevServerSetup.VTPServerPort) {
}
cComponentVTP::~cComponentVTP() {
}

29
server/componentVTP.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* $Id: componentVTP.h,v 1.1 2004/12/30 22:44:19 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_SERVERS_SERVERVTP_H
#define VDR_STREAMDEV_SERVERS_SERVERVTP_H
#include "server/component.h"
#include "server/connectionVTP.h"
#include <tools/socket.h>
#include <tools/select.h>
class cComponentVTP: public cServerComponent {
private:
cTBSocket m_Listen;
public:
cComponentVTP(void);
virtual ~cComponentVTP();
virtual cServerConnection *NewConnection(void) const;
};
inline cServerConnection *cComponentVTP::NewConnection(void) const {
return new cConnectionVTP;
}
#endif // VDR_STREAMDEV_SERVERS_SERVERVTP_H

172
server/connection.c Normal file
View File

@@ -0,0 +1,172 @@
/*
* $Id: connection.c,v 1.1 2004/12/30 22:44:19 lordjaxom Exp $
*/
#include "server/connection.h"
#include "server/setup.h"
#include "server/suspend.h"
#include "common.h"
#include <vdr/tools.h>
#include <string.h>
#include <errno.h>
cServerConnection::cServerConnection(const char *Protocol) {
m_RdBytes = 0;
m_WrBytes = 0;
m_WrOffs = 0;
m_DeferClose = false;
m_Protocol = Protocol;
}
cServerConnection::~cServerConnection() {
}
bool cServerConnection::CanAct(const cTBSelect &Select) {
if (Select.CanRead(*this)) {
int b;
if ((b = Read(m_RdBuf + m_RdBytes, sizeof(m_RdBuf) - m_RdBytes - 1)) < 0) {
esyslog("Streamdev: Read from client (%s) %s:%d failed: %s", m_Protocol,
(const char*)RemoteIp(), RemotePort(), strerror(errno));
return false;
}
if (b == 0) {
isyslog("Streamdev: Client (%s) %s:%d closed connection", m_Protocol,
(const char*)RemoteIp(), RemotePort());
return false;
}
m_RdBytes += b;
m_RdBuf[m_RdBytes] = '\0';
return ParseBuffer();
}
if (Select.CanWrite(*this)) {
int b;
if ((b = Write(m_WrBuf + m_WrOffs, m_WrBytes - m_WrOffs)) < 0) {
esyslog("Streamdev: Write to client (%s) %s:%d failed: %s", m_Protocol,
(const char*)RemoteIp(), RemotePort(), strerror(errno));
return false;
}
m_WrOffs += b;
if (m_WrOffs == m_WrBytes) {
m_WrBytes = 0;
m_WrOffs = 0;
}
}
if (m_WrBytes == 0) {
if (m_DeferClose)
return false;
Flushed();
}
return true;
}
bool cServerConnection::ParseBuffer(void) {
char *ep;
bool res;
while ((ep = strchr(m_RdBuf, '\012')) != NULL) {
*ep = '\0';
if (ep > m_RdBuf && *(ep-1) == '\015')
*(ep-1) = '\0';
Dprintf("IN: |%s|\n", m_RdBuf);
res = Command(m_RdBuf);
m_RdBytes -= ++ep - m_RdBuf;
if (m_RdBytes > 0)
memmove(m_RdBuf, ep, m_RdBytes);
if (res == false)
return false;
}
return true;
}
bool cServerConnection::Respond(const char *Message) {
uint len = strlen(Message);
if (m_WrBytes + len + 2 > sizeof(m_WrBuf)) {
esyslog("Streamdev: Output buffer overflow (%s) for %s:%d", m_Protocol,
(const char*)RemoteIp(), RemotePort());
return false;
}
Dprintf("OUT: |%s|\n", Message);
memcpy(m_WrBuf + m_WrBytes, Message, len);
m_WrBuf[m_WrBytes + len] = '\015';
m_WrBuf[m_WrBytes + len + 1] = '\012';
m_WrBytes += len + 2;
return true;
}
cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority) {
cDevice *device = NULL;
/*Dprintf("+ Statistics:\n");
Dprintf("+ Current Channel: %d\n", cDevice::CurrentChannel());
Dprintf("+ Current Device: %d\n", cDevice::ActualDevice()->CardIndex());
Dprintf("+ Transfer Mode: %s\n", cDevice::ActualDevice()
== cDevice::PrimaryDevice() ? "false" : "true");
Dprintf("+ Replaying: %s\n", cDevice::PrimaryDevice()->Replaying() ? "true"
: "false");*/
Dprintf(" * GetDevice(const cChannel*, int)\n");
Dprintf(" * -------------------------------\n");
device = cDevice::GetDevice(Channel, Priority);
Dprintf(" * Found following device: %p (%d)\n", device,
device ? device->CardIndex() + 1 : 0);
if (device == cDevice::ActualDevice())
Dprintf(" * is actual device\n");
if (!cSuspendCtl::IsActive() && StreamdevServerSetup.SuspendMode != smAlways)
Dprintf(" * NOT suspended\n");
if (!device || (device == cDevice::ActualDevice()
&& !cSuspendCtl::IsActive()
&& StreamdevServerSetup.SuspendMode != smAlways)) {
// mustn't switch actual device
// maybe a device would be free if THIS connection did turn off its streams?
Dprintf(" * trying again...\n");
const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel());
isyslog("streamdev-server: Detaching current receiver");
Detach();
device = cDevice::GetDevice(Channel, Priority);
Attach();
Dprintf(" * Found following device: %p (%d)\n", device,
device ? device->CardIndex() + 1 : 0);
if (device == cDevice::ActualDevice())
Dprintf(" * is actual device\n");
if (!cSuspendCtl::IsActive()
&& StreamdevServerSetup.SuspendMode != smAlways)
Dprintf(" * NOT suspended\n");
if (current && !TRANSPONDER(Channel, current))
Dprintf(" * NOT same transponder\n");
if (device && (device == cDevice::ActualDevice()
&& !cSuspendCtl::IsActive()
&& StreamdevServerSetup.SuspendMode != smAlways
&& current != NULL
&& !TRANSPONDER(Channel, current))) {
// now we would have to switch away live tv...let's see if live tv
// can be handled by another device
cDevice *newdev = NULL;
for (int i = 0; i < cDevice::NumDevices(); ++i) {
cDevice *dev = cDevice::GetDevice(i);
if (dev->ProvidesChannel(current, 0) && dev != device) {
newdev = dev;
break;
}
}
Dprintf(" * Found device for live tv: %p (%d)\n", newdev,
newdev ? newdev->CardIndex() + 1 : 0);
if (newdev == NULL || newdev == device)
// no suitable device to continue live TV, giving up...
device = NULL;
else
newdev->SwitchChannel(current, true);
}
}
return device;
}

87
server/connection.h Normal file
View File

@@ -0,0 +1,87 @@
/*
* $Id: connection.h,v 1.1 2004/12/30 22:44:19 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_SERVER_CONNECTION_H
#define VDR_STREAMDEV_SERVER_CONNECTION_H
#include "tools/socket.h"
#include "tools/select.h"
#include "common.h"
class cChannel;
class cDevice;
/* Basic capabilities of a straight text-based protocol, most functions
virtual to support more complicated protocols */
class cServerConnection: public cListObject, public cTBSocket {
private:
char m_RdBuf[8192];
uint m_RdBytes;
char m_WrBuf[8192];
uint m_WrBytes;
uint m_WrOffs;
const char *m_Protocol;
bool m_DeferClose;
public:
/* If you derive, specify a short string such as HTTP for Protocol, which
will be displayed in error messages */
cServerConnection(const char *Protocol);
virtual ~cServerConnection();
/* Gets called if the client has been accepted by the core */
virtual void Welcome(void) { }
/* Gets called if the client has been rejected by the core */
virtual void Reject(void) { DeferClose(); }
/* Adds itself to the Select object, if data can be received or if data is
to be sent. Override if necessary */
virtual void AddSelect(cTBSelect &Select) const;
/* Receives incoming data and calls ParseBuffer on it. Also writes queued
output data if possible. Override if necessary */
virtual bool CanAct(const cTBSelect &Select);
/* Called by CanAct(), parses the input buffer for full lines (terminated
either by '\012' or '\015\012') and calls Command on them, if any */
virtual bool ParseBuffer(void);
/* Will be called when a command terminated by a newline has been received */
virtual bool Command(char *Cmd) = 0;
/* Will put Message into the response queue, which will be sent in the next
server cycle. Note that Message will be line-terminated by Respond */
bool Respond(const char *Message);
/* Will make the socket close after sending all queued output data */
void DeferClose(void) { m_DeferClose = true; }
/* Will retrieve an unused device for transmitting data. Use the returned
cDevice in a following call to StartTransfer */
cDevice *GetDevice(const cChannel *Channel, int Priority);
virtual void Flushed(void) {}
virtual void Detach(void) = 0;
virtual void Attach(void) = 0;
};
class cServerConnections: public cList<cServerConnection> {
};
inline void cServerConnection::AddSelect(cTBSelect &Select) const {
if (m_WrBytes > 0)
Select.Add(*this, true);
if (m_WrBytes == 0 && m_RdBytes < sizeof(m_RdBuf) - 1)
Select.Add(*this, false);
}
#endif // VDR_STREAMDEV_SERVER_CONNECTION_H

180
server/connectionHTTP.c Normal file
View File

@@ -0,0 +1,180 @@
/*
* $Id: connectionHTTP.c,v 1.1 2004/12/30 22:44:19 lordjaxom Exp $
*/
#include "server/connectionHTTP.h"
#include "server/livestreamer.h"
#include "server/setup.h"
cConnectionHTTP::cConnectionHTTP(void): cServerConnection("HTTP") {
m_Channel = NULL;
m_ListChannel = NULL;
m_LiveStreamer = NULL;
m_Status = hsRequest;
m_StreamType = (eStreamType)StreamdevServerSetup.HTTPStreamType;
m_Startup = false;
}
cConnectionHTTP::~cConnectionHTTP() {
if (m_LiveStreamer != NULL) delete m_LiveStreamer;
}
void cConnectionHTTP::Detach(void) {
if (m_LiveStreamer != NULL) m_LiveStreamer->Detach();
}
void cConnectionHTTP::Attach(void) {
if (m_LiveStreamer != NULL) m_LiveStreamer->Attach();
}
bool cConnectionHTTP::Command(char *Cmd) {
switch (m_Status) {
case hsRequest:
if (strncmp(Cmd, "GET ", 4) == 0) return CmdGET(Cmd + 4);
else {
DeferClose();
m_Status = hsTransfer; // Ignore following lines
return Respond("HTTP/1.0 400 Bad Request");
}
break;
case hsHeaders:
if (*Cmd == '\0') {
if (m_ListChannel != NULL) {
m_Status = hsListing;
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: text/html")
&& Respond("")
&& Respond("<html><head><title>VDR Channel Listing</title></head>")
&& Respond("<body><ul>");
} else if (m_Channel == NULL) {
DeferClose();
return Respond("HTTP/1.0 404 not found");
}
m_Status = hsTransfer;
m_LiveStreamer = new cStreamdevLiveStreamer(0);
cDevice *device = GetDevice(m_Channel, 0);
if (device != NULL) {
device->SwitchChannel(m_Channel, false);
m_LiveStreamer->SetDevice(device);
if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, false)) {
m_Startup = true;
if (m_StreamType == stES && (m_Channel->Vpid() == 0
|| m_Channel->Vpid() == 1 || m_Channel->Vpid() == 0x1FFF)) {
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: audio/mpeg")
&& Respond((cTBString)"icy-name: " + m_Channel->Name())
&& Respond("");
} else {
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: video/mpeg")
&& Respond("");
}
}
}
DELETENULL(m_LiveStreamer);
DeferClose();
return Respond("HTTP/1.0 409 Channel not available");
}
break;
default:
break;
}
return true;
}
void cConnectionHTTP::Flushed(void) {
if (m_Status == hsListing) {
cTBString line;
if (m_ListChannel == NULL) {
Respond("</ul></body></html>");
DeferClose();
return;
}
if (m_ListChannel->GroupSep())
line.Format("<li>--- %s ---</li>", m_ListChannel->Name());
else
line.Format("<li><a href=\"http://%s:%d/%s\">%s</a></li>",
(const char*)LocalIp(), StreamdevServerSetup.HTTPServerPort,
m_ListChannel->GetChannelID().ToString(), m_ListChannel->Name());
if (!Respond(line))
DeferClose();
m_ListChannel = Channels.Next(m_ListChannel);
} else if (m_Startup) {
Dprintf("streamer start\n");
m_LiveStreamer->Start(this);
m_Startup = false;
}
}
bool cConnectionHTTP::CmdGET(char *Opts) {
cChannel *chan;
char *ep;
Opts = skipspace(Opts);
while (*Opts == '/')
++Opts;
if (strncasecmp(Opts, "PS/", 3) == 0) {
m_StreamType = stPS;
Opts+=3;
} else if (strncasecmp(Opts, "PES/", 4) == 0) {
m_StreamType = stPES;
Opts+=4;
} else if (strncasecmp(Opts, "TS/", 3) == 0) {
m_StreamType = stTS;
Opts+=3;
} else if (strncasecmp(Opts, "ES/", 3) == 0) {
m_StreamType = stES;
Opts+=3;
}
while (*Opts == '/')
++Opts;
for (ep = Opts + strlen(Opts); ep >= Opts && !isspace(*ep); --ep)
;
*ep = '\0';
if (strncmp(Opts, "channels.htm", 12) == 0) {
m_ListChannel = Channels.First();
m_Status = hsHeaders;
} else if ((chan = ChannelFromString(Opts)) != NULL) {
m_Channel = chan;
m_Status = hsHeaders;
}
return true;
}
#if 0
bool cHTTPConnection::Listing(void) {
cChannel *chan;
cTBString line;
Respond(200, "OK");
Respond("Content-Type: text/html");
Respond("");
Respond("<html><head><title>VDR Channel Listing</title></head>");
Respond("<body><ul>");
for (chan = Channels.First(); chan != NULL; chan = Channels.Next(chan)) {
if (chan->GroupSep() && !*chan->Name())
continue;
if (chan->GroupSep())
line.Format("<li>--- %s ---</li>", chan->Name());
else
line.Format("<li><a href=\"http://%s:%d/%s\">%s</a></li>",
(const char*)m_Socket.LocalIp(), StreamdevServerSetup.HTTPServerPort,
chan->GetChannelID().ToString(), chan->Name());
Respond(line);
}
Respond("</ul></body></html>");
m_DeferClose = true;
return true;
}
#endif

44
server/connectionHTTP.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* $Id: connectionHTTP.h,v 1.1 2004/12/30 22:44:18 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
#define VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
#include "connection.h"
#include <tools/select.h>
class cChannel;
class cStreamdevLiveStreamer;
class cConnectionHTTP: public cServerConnection {
private:
enum eHTTPStatus {
hsRequest,
hsHeaders,
hsTransfer,
hsListing,
};
cChannel *m_Channel;
cChannel *m_ListChannel;
cStreamdevLiveStreamer *m_LiveStreamer;
eStreamType m_StreamType;
eHTTPStatus m_Status;
bool m_Startup;
public:
cConnectionHTTP(void);
virtual ~cConnectionHTTP();
virtual void Detach(void);
virtual void Attach(void);
virtual bool Command(char *Cmd);
bool CmdGET(char *Opts);
virtual void Flushed(void);
};
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H

553
server/connectionVTP.c Normal file
View File

@@ -0,0 +1,553 @@
/*
* $Id: connectionVTP.c,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
*/
#include "server/connectionVTP.h"
#include "server/livestreamer.h"
#include "server/suspend.h"
#include "setup.h"
#include <vdr/tools.h>
#include <tools/select.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
/* VTP Response codes:
220: Service ready
221: Service closing connection
500: Syntax error or Command unrecognized
501: Wrong parameters or missing parameters
550: Action not done
551: Data connection not accepted
560: Channel not available currently
561: Capability not known
562: Pid not available currently
563: Recording not available (currently?)
*/
cConnectionVTP::cConnectionVTP(void): cServerConnection("VTP") {
m_StreamPIDS = false;
m_LiveStreamer = NULL;
m_ClientCaps = stTS;
memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count);
}
cConnectionVTP::~cConnectionVTP() {
if (m_LiveStreamer != NULL) delete m_LiveStreamer;
for (int idx = 0; idx < si_Count; ++idx)
if (m_DataSockets[idx] != NULL) delete m_DataSockets[idx];
}
void cConnectionVTP::Welcome(void) {
Respond(220, "Welcome to Video Disk Recorder (VTP)");
}
void cConnectionVTP::Reject(void) {
Respond(221, "Too many clients or client not allowed to connect");
cServerConnection::Reject();
}
void cConnectionVTP::Detach(void) {
if (m_LiveStreamer != NULL) m_LiveStreamer->Detach();
}
void cConnectionVTP::Attach(void) {
if (m_LiveStreamer != NULL) m_LiveStreamer->Attach();
}
bool cConnectionVTP::Command(char *Cmd) {
char *ep;
if ((ep = strchr(Cmd, ' ')) != NULL)
*(ep++) = '\0';
else
ep = Cmd + strlen(Cmd);
if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(ep);
else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(ep);
else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(ep);
else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(ep);
else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(ep);
else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(ep);
else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(ep);
else if (strcasecmp(Cmd, "DELF") == 0) return CmdDELF(ep);
else if (strcasecmp(Cmd, "ABRT") == 0) return CmdABRT(ep);
else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT(ep);
else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP(ep);
// Commands adopted from SVDRP
else if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(ep);
else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(ep);
else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(ep);
else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(ep);
else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(ep);
else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(ep);
else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(ep);
else
return Respond(500, (cTBString)"Unknown Command '" + Cmd + "'");
}
bool cConnectionVTP::CmdCAPS(char *Opts) {
if (strcasecmp(Opts, "TSPIDS") == 0) m_StreamPIDS = true;
else {
int idx = 0;
while (idx < st_Count && strcasecmp(Opts, StreamTypes[idx]) != 0)
++idx;
if (idx == st_Count)
return Respond(561, (cTBString)"Capability \"" + Opts + "\" not known");
m_ClientCaps = (eStreamType)idx;
}
return Respond(220, (cTBString)"Capability \"" + Opts + "\" accepted");
}
bool cConnectionVTP::CmdPROV(char *Opts) {
cChannel *chan;
int prio;
char *ep;
prio = strtol(Opts, &ep, 10);
if (ep == Opts || !isspace(*ep))
return Respond(501, "Use: PROV Priority Channel");
Opts = skipspace(ep);
if ((chan = ChannelFromString(Opts)) == NULL)
return Respond(550, (cTBString)"Undefined channel \"" + Opts + "\"");
return GetDevice(chan, prio) != NULL
? Respond(220, "Channel available")
: Respond(560, "Channel not available");
}
bool cConnectionVTP::CmdPORT(char *Opts) {
uint id, dataport = 0;
char dataip[20];
char *ep, *ipoffs;
int n;
id = strtoul(Opts, &ep, 10);
if (ep == Opts || !isspace(*ep))
return Respond(500, "Use: PORT Id Destination");
if (id >= si_Count)
return Respond(501, "Wrong connection id " + cTBString::Number(id));
Opts = skipspace(ep);
n = 0;
ipoffs = dataip;
while ((ep = strchr(Opts, ',')) != NULL) {
if (n < 4) {
memcpy(ipoffs, Opts, ep - Opts);
ipoffs += ep - Opts;
if (n < 3) *(ipoffs++) = '.';
} else if (n == 4) {
*ep = 0;
dataport = strtoul(Opts, NULL, 10) << 8;
} else
break;
Opts = ep + 1;
++n;
}
*ipoffs = '\0';
if (n != 5)
return Respond(501, "Argument count invalid (must be 6 values)");
dataport |= strtoul(Opts, NULL, 10);
isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport);
m_DataSockets[id] = new cTBSocket(SOCK_STREAM);
if (!m_DataSockets[id]->Connect(dataip, dataport)) {
esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
dataip, dataport, strerror(errno));
DELETENULL(m_DataSockets[id]);
return Respond(551, "Couldn't open data connection");
}
if (id == siLive)
m_LiveStreamer->Start(m_DataSockets[id]);
return Respond(220, "Port command ok, data connection opened");
}
bool cConnectionVTP::CmdTUNE(char *Opts) {
const cChannel *chan;
cDevice *dev;
if ((chan = ChannelFromString(Opts)) == NULL)
return Respond(550, (cTBString)"Undefined channel \"" + Opts + "\"");
if ((dev = GetDevice(chan, 0)) == NULL)
return Respond(560, "Channel not available");
if (!dev->SwitchChannel(chan, false))
return Respond(560, "Channel not available");
delete m_LiveStreamer;
m_LiveStreamer = new cStreamdevLiveStreamer(1);
m_LiveStreamer->SetChannel(chan, m_ClientCaps, m_StreamPIDS);
m_LiveStreamer->SetDevice(dev);
return Respond(220, "Channel tuned");
}
bool cConnectionVTP::CmdADDP(char *Opts) {
int pid;
char *end;
pid = strtoul(Opts, &end, 10);
if (end == Opts || (*end != '\0' && *end != ' '))
return Respond(500, "Use: ADDP Pid");
return m_LiveStreamer && m_LiveStreamer->SetPid(pid, true)
? Respond(220, "Pid " + cTBString::Number(pid) + " available")
: Respond(560, "Pid " + cTBString::Number(pid) + " not available");
}
bool cConnectionVTP::CmdDELP(char *Opts) {
int pid;
char *end;
pid = strtoul(Opts, &end, 10);
if (end == Opts || (*end != '\0' && *end != ' '))
return Respond(500, "Use: DELP Pid");
return m_LiveStreamer && m_LiveStreamer->SetPid(pid, false)
? Respond(220, "Pid " + cTBString::Number(pid) + " stopped")
: Respond(560, "Pid " + cTBString::Number(pid) + " not transferring");
}
bool cConnectionVTP::CmdADDF(char *Opts) {
#if VDRVERSNUM >= 10300
int pid, tid, mask;
char *ep;
if (m_LiveStreamer == NULL)
return Respond(560, "Can't set filters without a stream");
pid = strtol(Opts, &ep, 10);
if (ep == Opts || (*ep != ' '))
return Respond(500, "Use: ADDF Pid Tid Mask");
Opts = skipspace(ep);
tid = strtol(Opts, &ep, 10);
if (ep == Opts || (*ep != ' '))
return Respond(500, "Use: ADDF Pid Tid Mask");
Opts = skipspace(ep);
mask = strtol(Opts, &ep, 10);
if (ep == Opts || (*ep != '\0' && *ep != ' '))
return Respond(500, "Use: ADDF Pid Tid Mask");
return m_LiveStreamer->SetFilter(pid, tid, mask, true)
? Respond(220, "Filter " + cTBString::Number(pid) + " transferring")
: Respond(560, "Filter " + cTBString::Number(pid) + " not available");
#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;
if (m_LiveStreamer == NULL)
return Respond(560, "Can't delete filters without a stream");
pid = strtol(Opts, &ep, 10);
if (ep == Opts || (*ep != ' '))
return Respond(500, "Use: DELF Pid Tid Mask");
Opts = skipspace(ep);
tid = strtol(Opts, &ep, 10);
if (ep == Opts || (*ep != ' '))
return Respond(500, "Use: DELF Pid Tid Mask");
Opts = skipspace(ep);
mask = strtol(Opts, &ep, 10);
if (ep == Opts || (*ep != '\0' && *ep != ' '))
return Respond(500, "Use: DELF Pid Tid Mask");
return m_LiveStreamer->SetFilter(pid, tid, mask, false)
? Respond(220, "Filter " + cTBString::Number(pid) + " stopped")
: Respond(560, "Filter " + cTBString::Number(pid) + " not transferring");
#else
return Respond(500, "DELF known but unimplemented with VDR < 1.3.0");
#endif
}
bool cConnectionVTP::CmdABRT(char *Opts) {
uint id;
char *ep;
id = strtoul(Opts, &ep, 10);
if (ep == Opts || (*ep != '\0' && *ep != ' '))
return Respond(500, "Use: ABRT Id");
time_t st = time_ms();
if (id == siLive)
DELETENULL(m_LiveStreamer);
Dprintf("ABRT took %ld ms\n", time_ms() - st);
DELETENULL(m_DataSockets[id]);
return Respond(220, "Data connection closed");
}
bool cConnectionVTP::CmdQUIT(char *Opts) {
if (!Respond(221, "Video Disk Recorder closing connection"))
return false;
DeferClose();
return true;
}
bool cConnectionVTP::CmdSUSP(char *Opts) {
if (StreamdevServerSetup.SuspendMode == smAlways || cSuspendCtl::IsActive())
return Respond(220, "Server is suspended");
else if (StreamdevServerSetup.SuspendMode == smOffer
&& StreamdevServerSetup.AllowSuspend) {
cControl::Launch(new cSuspendCtl);
return Respond(220, "Server is suspended");
} else
return Respond(550, "Client may not suspend server");
}
// Functions adopted from SVDRP
#define INIT_WRAPPER() bool _res = true
#define Reply(x...) _res &= ReplyWrapper(x)
#define EXIT_WRAPPER() return _res
bool cConnectionVTP::ReplyWrapper(int Code, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
char *buffer;
vasprintf(&buffer, fmt, ap);
int npos;
if (buffer[npos = strlen(buffer)-1] == '\n')
buffer[npos] = '\0';
bool res = Respond(Code, buffer);
free(buffer);
return res;
}
bool cConnectionVTP::CmdLSTE(char *Option) {
#if VDRVERSNUM < 10300
cMutexLock MutexLock;
#else
cSchedulesLock MutexLock;
#endif
INIT_WRAPPER();
/* we need to create a blocking copy of the socket here */
int dupfd = dup(*this);
fcntl(dupfd, F_SETFL, fcntl(dupfd, F_GETFL) & ~O_NONBLOCK);
#if VDRVERSNUM < 10300
const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
#else
const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
#endif
if (Schedules) {
FILE *f = fdopen(dupfd, "w");
if (f) {
Schedules->Dump(f, "215-");
fflush(f);
Reply(215, "End of EPG data");
fclose(f);
}
else
Reply(451, "Can't open file connection");
}
else
Reply(451, "Can't get EPG data");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdLSTR(char *Option) {
INIT_WRAPPER();
bool recordings = Recordings.Load();
Recordings.Sort();
if (*Option) {
if (isnumber(Option)) {
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
if (recording) {
if (recording->Summary()) {
char *summary = strdup(recording->Summary());
Reply(250, "%s", strreplace(summary,'\n','|'));
free(summary);
}
else
Reply(550, "No summary availabe");
}
else
Reply(550, "Recording \"%s\" not found", Option);
}
else
Reply(501, "Error in recording number \"%s\"", Option);
}
else if (recordings) {
cRecording *recording = Recordings.First();
while (recording) {
Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
recording = Recordings.Next(recording);
}
}
else
Reply(550, "No recordings available");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdDELR(char *Option) {
INIT_WRAPPER();
if (*Option) {
if (isnumber(Option)) {
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
if (recording) {
if (recording->Delete())
Reply(250, "Recording \"%s\" deleted", Option);
else
Reply(554, "Error while deleting recording!");
}
else
Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
}
else
Reply(501, "Error in recording number \"%s\"", Option);
}
else
Reply(501, "Missing recording number");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdLSTT(char *Option) {
INIT_WRAPPER();
if (*Option) {
if (isnumber(Option)) {
cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
if (timer)
Reply(250, "%d %s", timer->Index() + 1, timer->ToText(true));
else
Reply(501, "Timer \"%s\" not defined", Option);
}
else
Reply(501, "Error in timer number \"%s\"", Option);
}
else if (Timers.Count()) {
for (int i = 0; i < Timers.Count(); i++) {
cTimer *timer = Timers.Get(i);
if (timer)
Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, timer->ToText(true));
else
Reply(501, "Timer \"%d\" not found", i + 1);
}
}
else
Reply(550, "No timers defined");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdMODT(char *Option) {
INIT_WRAPPER();
if (*Option) {
char *tail;
int n = strtol(Option, &tail, 10);
if (tail && tail != Option) {
tail = skipspace(tail);
cTimer *timer = Timers.Get(n - 1);
if (timer) {
cTimer t = *timer;
if (strcasecmp(tail, "ON") == 0)
#if VDRVERSNUM < 10300
t.SetActive(taActive);
#else
t.SetFlags(tfActive);
#endif
else if (strcasecmp(tail, "OFF") == 0)
#if VDRVERSNUM < 10300
t.SetActive(taInactive);
#else
t.ClrFlags(tfActive);
#endif
else if (!t.Parse(tail)) {
Reply(501, "Error in timer settings");
EXIT_WRAPPER();
}
*timer = t;
Timers.Save();
#if VDRVERSNUM < 10300
isyslog("timer %d modified (%s)", timer->Index() + 1,
timer->Active() ? "active" : "inactive");
#else
isyslog("timer %d modified (%s)", timer->Index() + 1,
timer->HasFlags(tfActive) ? "active" : "inactive");
#endif
Reply(250, "%d %s", timer->Index() + 1, timer->ToText(true));
}
else
Reply(501, "Timer \"%d\" not defined", n);
}
else
Reply(501, "Error in timer number");
}
else
Reply(501, "Missing timer settings");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdNEWT(char *Option) {
INIT_WRAPPER();
if (*Option) {
cTimer *timer = new cTimer;
if (timer->Parse(Option)) {
cTimer *t = Timers.GetTimer(timer);
if (!t) {
Timers.Add(timer);
Timers.Save();
isyslog("timer %d added", timer->Index() + 1);
Reply(250, "%d %s", timer->Index() + 1, timer->ToText(true));
EXIT_WRAPPER();
}
else
Reply(550, "Timer already defined: %d %s", t->Index() + 1, t->ToText(true));
}
else
Reply(501, "Error in timer settings");
delete timer;
}
else
Reply(501, "Missing timer settings");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdDELT(char *Option) {
INIT_WRAPPER();
if (*Option) {
if (isnumber(Option)) {
cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
if (timer) {
if (!timer->Recording()) {
Timers.Del(timer);
Timers.Save();
isyslog("timer %s deleted", Option);
Reply(250, "Timer \"%s\" deleted", Option);
}
else
Reply(550, "Timer \"%s\" is recording", Option);
}
else
Reply(501, "Timer \"%s\" not defined", Option);
}
else
Reply(501, "Error in timer number \"%s\"", Option);
}
else
Reply(501, "Missing timer number");
EXIT_WRAPPER();
}
bool cConnectionVTP::Respond(int Code, const char *Message) {
cTBString pkt;
if (Code < 0)
pkt.Format("%03d-%s", -Code, Message);
else
pkt.Format("%03d %s", Code, Message);
return cServerConnection::Respond((const char*)pkt);
}

57
server/connectionVTP.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
#include "server/connection.h"
class cDevice;
class cTBSocket;
class cStreamdevLiveStreamer;
class cConnectionVTP: public cServerConnection {
private:
cTBSocket *m_DataSockets[si_Count];
eStreamType m_ClientCaps;
bool m_StreamPIDS;
cStreamdevLiveStreamer *m_LiveStreamer;
// Members adopted from SVDRP
cRecordings Recordings;
public:
cConnectionVTP(void);
virtual ~cConnectionVTP();
virtual void Welcome(void);
virtual void Reject(void);
virtual void Detach(void);
virtual void Attach(void);
bool Command(char *Cmd);
bool CmdCAPS(char *Opts);
bool CmdPROV(char *Opts);
bool CmdPORT(char *Opts);
bool CmdTUNE(char *Opts);
bool CmdADDP(char *Opts);
bool CmdDELP(char *Opts);
bool CmdADDF(char *Opts);
bool CmdDELF(char *Opts);
bool CmdABRT(char *Opts);
bool CmdQUIT(char *Opts);
bool CmdSUSP(char *Opts);
// Commands adopted from SVDRP
bool ReplyWrapper(int Code, const char *fmt, ...);
bool CmdLSTE(char *Opts);
bool CmdLSTR(char *Opts);
bool CmdDELR(char *Opts);
bool CmdLSTT(char *Opts);
bool CmdMODT(char *Opts);
bool CmdNEWT(char *Opts);
bool CmdDELT(char *Opts);
bool Respond(int Code, const char *Message);
};
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H

64
server/livefilter.c Normal file
View File

@@ -0,0 +1,64 @@
/*
* $Id: livefilter.c,v 1.1 2004/12/30 22:44:27 lordjaxom Exp $
*/
#include "server/livefilter.h"
#include "server/livestreamer.h"
#include "common.h"
#if VDRVERSNUM >= 10300
cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevLiveStreamer *Streamer) {
m_Streamer = Streamer;
}
cStreamdevLiveFilter::~cStreamdevLiveFilter() {
}
void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data,
int Length) {
static time_t firsterr = 0;
static int errcnt = 0;
static bool showerr = true;
uchar buffer[TS_SIZE];
int length = Length;
int pos = 0;
while (length > 0) {
int chunk = min(length, TS_SIZE - 5);
buffer[0] = TS_SYNC_BYTE;
buffer[1] = (Pid >> 8) & 0xff;
buffer[2] = Pid & 0xff;
buffer[3] = Tid;
buffer[4] = (uchar)chunk;
memcpy(buffer + 5, Data + pos, chunk);
length -= chunk;
pos += chunk;
int p = m_Streamer->Put(buffer, TS_SIZE);
if (p != TS_SIZE) {
++errcnt;
if (showerr) {
if (firsterr == 0)
firsterr = time_ms();
else if (firsterr + BUFOVERTIME > time_ms() && errcnt > BUFOVERCOUNT) {
esyslog("ERROR: too many buffer overflows, logging stopped");
showerr = false;
firsterr = time_ms();
}
} else if (firsterr + BUFOVERTIME < time_ms()) {
showerr = true;
firsterr = 0;
errcnt = 0;
}
if (showerr)
esyslog("ERROR: ring buffer overflow (%d bytes dropped)", TS_SIZE - p);
else
firsterr = time_ms();
}
}
}
#endif // VDRVERSNUM >= 10300

29
server/livefilter.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* $Id: livefilter.h,v 1.1 2004/12/30 22:44:27 lordjaxom Exp $
*/
#ifndef VDR_STREAMEV_LIVEFILTER_H
#define VDR_STREAMEV_LIVEFILTER_H
#include <vdr/config.h>
# if VDRVERSNUM >= 10300
#include <vdr/filter.h>
class cStreamdevLiveFilter: public cFilter {
friend class cStreamdevLiveStreamer;
private:
cStreamdevLiveStreamer *m_Streamer;
protected:
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
public:
cStreamdevLiveFilter(cStreamdevLiveStreamer *Streamer);
virtual ~cStreamdevLiveFilter();
};
# endif // VDRVERSNUM >= 10300
#endif // VDR_STREAMEV_LIVEFILTER_H

242
server/livestreamer.c Normal file
View File

@@ -0,0 +1,242 @@
#include <vdr/ringbuffer.h>
#include "server/livestreamer.h"
#include "remux/ts2ps.h"
#include "remux/ts2es.h"
#include "common.h"
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer,
int Ca, int Priority,
int Pid1, int Pid2, int Pid3, int Pid4,
int Pid5, int Pid6, int Pid7, int Pid8,
int Pid9, int Pid10, int Pid11, int Pid12,
int Pid13, int Pid14, int Pid15, int Pid16):
cReceiver(Ca, Priority, 16,
Pid1, Pid2, Pid3, Pid4, Pid5, Pid6, Pid7, Pid8,
Pid9, Pid10, Pid11, Pid12, Pid13, Pid14, Pid15, Pid16) {
m_Streamer = Streamer;
}
cStreamdevLiveReceiver::~cStreamdevLiveReceiver() {
Dprintf("Killing live receiver\n");
Detach();
}
void cStreamdevLiveReceiver::Receive(uchar *Data, int Length) {
static time_t firsterr = 0;
static int errcnt = 0;
static bool showerr = true;
int p = m_Streamer->Put(Data, Length);
if (p != Length) {
++errcnt;
if (showerr) {
if (firsterr == 0)
firsterr = time_ms();
else if (firsterr + BUFOVERTIME > time_ms() && errcnt > BUFOVERCOUNT) {
esyslog("ERROR: too many buffer overflows, logging stopped");
showerr = false;
firsterr = time_ms();
}
} else if (firsterr + BUFOVERTIME < time_ms()) {
showerr = true;
firsterr = 0;
errcnt = 0;
}
if (showerr)
esyslog("ERROR: ring buffer overflow (%d bytes dropped)", Length - p);
else
firsterr = time_ms();
}
}
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority):
cStreamdevStreamer("Live streamer") {
m_Priority = Priority;
m_Channel = NULL;
m_Device = NULL;
m_Receiver = NULL;
m_Remux = NULL;
m_Buffer = NULL;
m_Sequence = 0;
#if VDRVERSNUM >= 10300
m_Filter = NULL;
#endif
memset(m_Pids, 0, sizeof(m_Pids));
}
cStreamdevLiveStreamer::~cStreamdevLiveStreamer() {
Dprintf("Desctructing Live streamer\n");
delete m_Receiver;
delete m_Remux;
#if VDRVERSNUM >= 10300
delete m_Filter;
#endif
free(m_Buffer);
}
void cStreamdevLiveStreamer::Detach(void) {
m_Device->Detach(m_Receiver);
}
void cStreamdevLiveStreamer::Attach(void) {
m_Device->AttachReceiver(m_Receiver);
}
void cStreamdevLiveStreamer::Start(cTBSocket *Socket) {
Dprintf("LIVESTREAMER START\n");
cStreamdevStreamer::Start(Socket);
}
bool cStreamdevLiveStreamer::SetPid(int Pid, bool On) {
int idx;
bool haspids = false;
if (On) {
for (idx = 0; idx < MAXRECEIVEPIDS; ++idx) {
if (m_Pids[idx] == Pid)
return true; // No change needed
else if (m_Pids[idx] == 0) {
m_Pids[idx] = Pid;
haspids = true;
break;
}
}
if (idx == MAXRECEIVEPIDS) {
esyslog("ERROR: Streamdev: No free slot to receive pid %d\n", Pid);
return false;
}
} else {
for (idx = 0; idx < MAXRECEIVEPIDS; ++idx) {
if (m_Pids[idx] == Pid)
m_Pids[idx] = 0;
else if (m_Pids[idx] != 0)
haspids = true;
}
}
DELETENULL(m_Receiver);
if (haspids) {
Dprintf("Creating Receiver to respect changed pids\n");
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->Ca(), m_Priority,
m_Pids[0], m_Pids[1], m_Pids[2], m_Pids[3],
m_Pids[4], m_Pids[5], m_Pids[6], m_Pids[7],
m_Pids[8], m_Pids[9], m_Pids[10], m_Pids[11],
m_Pids[12], m_Pids[13], m_Pids[14], m_Pids[15]);
if (m_Device != NULL) {
Dprintf("Attaching new receiver\n");
m_Device->AttachReceiver(m_Receiver);
}
}
return true;
}
bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, int StreamType,
bool StreamPIDS) {
Dprintf("Initializing Remuxer for full channel transfer\n");
printf("ca pid: %d\n", Channel->Ca());
m_Channel = Channel;
switch (StreamType) {
case stES:
{
int pid = ISRADIO(Channel) ? Channel->Apid1() : Channel->Vpid();
m_Remux = new cTS2ESRemux(pid);
return SetPid(pid, true);
}
case stPES:
m_Remux = new cTS2PSRemux(Channel->Vpid(), Channel->Apid1(),
Channel->Apid2(), Channel->Dpid1(), 0, false);
return SetPid(Channel->Vpid(), true)
&& SetPid(Channel->Apid1(), true)
&& SetPid(Channel->Apid2(), true)
&& SetPid(Channel->Dpid1(), true);
break;
case stPS:
m_Remux = new cTS2PSRemux(Channel->Vpid(), Channel->Apid1(), 0, 0, 0, true);
return SetPid(Channel->Vpid(), true)
&& SetPid(Channel->Apid1(), true);
break;
case stTS:
if (!StreamPIDS) {
return SetPid(Channel->Vpid(), true)
&& SetPid(Channel->Apid1(), true)
&& SetPid(Channel->Apid2(), true)
&& SetPid(Channel->Dpid1(), true);
}
Dprintf("pid streaming mode\n");
return true;
break;
}
return false;
}
bool cStreamdevLiveStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask,
bool On) {
#if VDRVERSNUM >= 10300
Dprintf("setting filter\n");
if (On) {
if (m_Filter == NULL) {
m_Filter = new cStreamdevLiveFilter(this);
Dprintf("attaching filter to device\n");
m_Device->AttachFilter(m_Filter);
}
m_Filter->Set(Pid, Tid, Mask);
} else if (m_Filter != NULL)
m_Filter->Del(Pid, Tid, Mask);
return true;
#else
return false;
#endif
}
uchar *cStreamdevLiveStreamer::Process(const uchar *Data, int &Count,
int &Result) {
uchar *remuxed = m_Remux != NULL ? m_Remux->Process(Data, Count, Result)
: cStreamdevStreamer::Process(Data, Count, Result);
if (remuxed) {
/*if (Socket()->Type() == SOCK_DGRAM) {
free(m_Buffer);
Result += 12;
m_Buffer = MALLOC(uchar, Result);
m_Buffer[0] = 0x01;
m_Buffer[1] = 0x02;
m_Buffer[2] = 0x03;
m_Buffer[3] = 0x04;
m_Buffer[4] = (Result & 0xff000000) >> 24;
m_Buffer[5] = (Result & 0xff0000) >> 16;
m_Buffer[6] = (Result & 0xff00) >> 8;
m_Buffer[7] = (Result & 0xff);
m_Buffer[8] = (m_Sequence & 0xff000000) >> 24;
m_Buffer[9] = (m_Sequence & 0xff0000) >> 16;
m_Buffer[10] = (m_Sequence & 0xff00) >> 8;
m_Buffer[11] = (m_Sequence & 0xff);
memcpy(m_Buffer + 12, Data, Result - 12);
if (m_Sequence++ == 0x7fffffff)
m_Sequence = 0;
return m_Buffer;
}*/
return remuxed;
}
return NULL;
}
cTBString cStreamdevLiveStreamer::Report(void) {
cTBString result;
if (m_Device != NULL)
result += "+- Device is " + cTBString::Number(m_Device->CardIndex()) + "\n";
if (m_Receiver != NULL)
result += "+- Receiver is allocated\n";
result += "+- Pids are ";
for (int i = 0; i < MAXRECEIVEPIDS; ++i)
if (m_Pids[i] != 0)
result += cTBString::Number(m_Pids[i]) + ", ";
result += "\n";
return result;
}

70
server/livestreamer.h Normal file
View File

@@ -0,0 +1,70 @@
#ifndef VDR_STREAMDEV_LIVESTREAMER_H
#define VDR_STREAMDEV_LIVESTREAMER_H
#include <vdr/config.h>
#include <vdr/receiver.h>
#include "server/streamer.h"
#include "server/livefilter.h"
#include "common.h"
#if MAXRECEIVEPIDS < 16
# error Too few receiver pids allowed! Please contact sascha@akv-soft.de!
#endif
class cTSRemux;
class cStreamdevLiveReceiver: public cReceiver {
friend class cStreamdevLiveStreamer;
private:
cStreamdevLiveStreamer *m_Streamer;
protected:
virtual void Receive(uchar *Data, int Length);
public:
cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, int Priority, int Ca,
int Pid1 = 0, int Pid2 = 0, int Pid3 = 0, int Pid4 = 0,
int Pid5 = 0, int Pid6 = 0, int Pid7 = 0, int Pid8 = 0,
int Pid9 = 0, int Pid10 = 0, int Pid11 = 0, int Pid12 = 0,
int Pid13 = 0, int Pid14 = 0, int Pid15 = 0, int Pid16 = 0);
virtual ~cStreamdevLiveReceiver();
};
class cStreamdevLiveStreamer: public cStreamdevStreamer {
private:
int m_Priority;
int m_Pids[MAXRECEIVEPIDS];
const cChannel *m_Channel;
cDevice *m_Device;
cStreamdevLiveReceiver *m_Receiver;
cTSRemux *m_Remux;
uchar *m_Buffer;
int m_Sequence;
#if VDRVERSNUM >= 10300
cStreamdevLiveFilter *m_Filter;
#endif
protected:
virtual uchar *Process(const uchar *Data, int &Count, int &Result);
public:
cStreamdevLiveStreamer(int Priority);
virtual ~cStreamdevLiveStreamer();
void SetDevice(cDevice *Device) { m_Device = Device; }
bool SetPid(int Pid, bool On);
bool SetChannel(const cChannel *Channel, int StreamType, bool StreamPIDS);
bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On);
virtual void Detach(void);
virtual void Attach(void);
virtual void Start(cTBSocket *Socket);
// Statistical purposes:
virtual cTBString Report(void);
};
#endif // VDR_STREAMDEV_LIVESTREAMER_H

148
server/server.c Normal file
View File

@@ -0,0 +1,148 @@
/*
* $Id: server.c,v 1.1 2004/12/30 22:44:20 lordjaxom Exp $
*/
#include "server/server.h"
#include "server/componentVTP.h"
#include "server/componentHTTP.h"
#include "server/setup.h"
#include <vdr/tools.h>
#include <tools/select.h>
#include <string.h>
#include <errno.h>
cSVDRPhosts StreamdevHosts;
cStreamdevServer *cStreamdevServer::m_Instance = NULL;
cStreamdevServer::cStreamdevServer(void)
#if VDRVERSNUM >= 10300
: cThread("Streamdev: server")
#endif
{
m_Active = false;
StreamdevHosts.Load(AddDirectory(cPlugin::ConfigDirectory(),
"streamdevhosts.conf"), true);
}
cStreamdevServer::~cStreamdevServer() {
if (m_Active) Stop();
}
void cStreamdevServer::Init(void) {
if (m_Instance == NULL) {
m_Instance = new cStreamdevServer;
if (StreamdevServerSetup.StartVTPServer)
m_Instance->Register(new cComponentVTP);
if (StreamdevServerSetup.StartHTTPServer)
m_Instance->Register(new cComponentHTTP);
m_Instance->Start();
}
}
void cStreamdevServer::Exit(void) {
if (m_Instance != NULL) {
m_Instance->Stop();
DELETENULL(m_Instance);
}
}
void cStreamdevServer::Stop(void) {
m_Active = false;
Cancel(3);
}
void cStreamdevServer::Register(cServerComponent *Server) {
m_Servers.Add(Server);
}
void cStreamdevServer::Action(void) {
cTBSelect select;
#if VDRVERSNUM < 10300
isyslog("Streamdev: Server thread started (pid=%d)", getpid());
#endif
m_Active = true;
/* Initialize Server components, deleting those that failed */
for (cServerComponent *c = m_Servers.First(); c;) {
cServerComponent *next = m_Servers.Next(c);
if (!c->Init())
m_Servers.Del(c);
c = next;
}
if (!m_Servers.Count()) {
esyslog("Streamdev: No server components registered, exiting");
m_Active = false;
}
while (m_Active) {
select.Clear();
/* Ask all Server components to register to the selector */
for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c))
c->AddSelect(select);
/* Ask all Client connections to register to the selector */
for (cServerConnection *s = m_Clients.First(); s; s = m_Clients.Next(s))
s->AddSelect(select);
if (select.Select(1000) < 0) {
if (!m_Active) // Exit was requested while polling
continue;
esyslog("Streamdev: Fatal error, server exiting: %s", strerror(errno));
m_Active = false;
}
/* Ask all Server components to act on signalled sockets */
for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c)) {
cServerConnection *client;
if ((client = c->CanAct(select)) != NULL) {
m_Clients.Add(client);
if (m_Clients.Count() > StreamdevServerSetup.MaxClients) {
esyslog("Streamdev: Too many clients, rejecting %s:%d",
(const char*)client->RemoteIp(), client->RemotePort());
client->Reject();
} else if (!StreamdevHosts.Acceptable(client->RemoteIpAddr())) {
esyslog("Streamdev: Client from %s:%d not allowed to connect",
(const char*)client->RemoteIp(), client->RemotePort());
client->Reject();
} else
client->Welcome();
}
}
/* Ask all Client connections to act on signalled sockets */
for (cServerConnection *s = m_Clients.First(); s;) {
cServerConnection *next = m_Clients.Next(s);
if (!s->CanAct(select)) {
isyslog("Streamdev: Closing connection to %s:%d",
(const char*)s->RemoteIp(), s->RemotePort());
s->Close();
m_Clients.Del(s);
}
s = next;
}
}
while (m_Clients.Count()) {
cServerConnection *client = m_Clients.First();
client->Close();
m_Clients.Del(client);
}
while (m_Servers.Count()) {
cServerComponent *server = m_Servers.First();
server->Exit();
m_Servers.Del(server);
}
#if VDRVERSNUM < 10300
isyslog("Streamdev: Server thread stopped");
#endif
}

44
server/server.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* $Id: server.h,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_SERVER_H
#define VDR_STREAMDEV_SERVER_H
#include <vdr/thread.h>
#include "server/component.h"
#include "server/connection.h"
class cStreamdevServer: public cThread {
private:
bool m_Active;
cServerComponents m_Servers;
cServerConnections m_Clients;
static cStreamdevServer *m_Instance;
protected:
virtual void Action(void);
void Stop(void);
public:
cStreamdevServer(void);
virtual ~cStreamdevServer();
void Register(cServerComponent *Server);
static void Init(void);
static void Exit(void);
static bool Active(void);
};
inline bool cStreamdevServer::Active(void) {
return m_Instance && m_Instance->m_Clients.Count() > 0;
}
extern cSVDRPhosts StreamdevHosts;
#endif // VDR_STREAMDEV_SERVER_H

94
server/setup.c Normal file
View File

@@ -0,0 +1,94 @@
/*
* $Id: setup.c,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
*/
#include <vdr/menuitems.h>
#include "server/setup.h"
#include "server/server.h"
#include "i18n.h"
cStreamdevServerSetup StreamdevServerSetup;
cStreamdevServerSetup::cStreamdevServerSetup(void) {
MaxClients = 5;
StartVTPServer = true;
VTPServerPort = 2004;
StartHTTPServer = true;
HTTPServerPort = 3000;
HTTPStreamType = stPES;
SuspendMode = smOffer;
AllowSuspend = false;
strcpy(VTPBindIP, "0.0.0.0");
strcpy(HTTPBindIP, "0.0.0.0");
}
bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) {
if (strcmp(Name, "MaxClients") == 0) MaxClients = atoi(Value);
else if (strcmp(Name, "StartServer") == 0) StartVTPServer = atoi(Value);
else if (strcmp(Name, "ServerPort") == 0) VTPServerPort = atoi(Value);
else if (strcmp(Name, "VTPBindIP") == 0) strcpy(VTPBindIP, Value);
else if (strcmp(Name, "StartHTTPServer") == 0) StartHTTPServer = atoi(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, "SuspendMode") == 0) SuspendMode = atoi(Value);
else if (strcmp(Name, "AllowSuspend") == 0) AllowSuspend = atoi(Value);
else return false;
return true;
}
cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) {
m_NewSetup = StreamdevServerSetup;
AddCategory (tr("Common Settings"));
AddRangeEdit(tr("Maximum Number of Clients"), m_NewSetup.MaxClients, 0, 100);
AddSuspEdit (tr("Suspend behaviour"), m_NewSetup.SuspendMode);
AddBoolEdit (tr("Client may suspend"), m_NewSetup.AllowSuspend);
AddCategory (tr("VDR-to-VDR Server"));
AddBoolEdit (tr("Start VDR-to-VDR Server"), m_NewSetup.StartVTPServer);
AddShortEdit(tr("VDR-to-VDR Server Port"), m_NewSetup.VTPServerPort);
AddIpEdit (tr("Bind to IP"), m_NewSetup.VTPBindIP);
AddCategory (tr("HTTP Server"));
AddBoolEdit (tr("Start HTTP Server"), m_NewSetup.StartHTTPServer);
AddShortEdit(tr("HTTP Server Port"), m_NewSetup.HTTPServerPort);
AddTypeEdit (tr("HTTP Streamtype"), m_NewSetup.HTTPStreamType);
AddIpEdit (tr("Bind to IP"), m_NewSetup.HTTPBindIP);
SetCurrent(Get(1));
}
cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() {
}
void cStreamdevServerMenuSetupPage::Store(void) {
bool restart = false;
if (m_NewSetup.StartVTPServer != StreamdevServerSetup.StartVTPServer
|| m_NewSetup.VTPServerPort != StreamdevServerSetup.VTPServerPort
|| strcmp(m_NewSetup.VTPBindIP, StreamdevServerSetup.VTPBindIP) != 0
|| m_NewSetup.StartHTTPServer != StreamdevServerSetup.StartHTTPServer
|| m_NewSetup.HTTPServerPort != StreamdevServerSetup.HTTPServerPort
|| strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0) {
restart = true;
cStreamdevServer::Exit();
}
SetupStore("MaxClients", m_NewSetup.MaxClients);
SetupStore("StartServer", m_NewSetup.StartVTPServer);
SetupStore("ServerPort", m_NewSetup.VTPServerPort);
SetupStore("VTPBindIP", m_NewSetup.VTPBindIP);
SetupStore("StartHTTPServer", m_NewSetup.StartHTTPServer);
SetupStore("HTTPServerPort", m_NewSetup.HTTPServerPort);
SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType);
SetupStore("HTTPBindIP", m_NewSetup.HTTPBindIP);
SetupStore("SuspendMode", m_NewSetup.SuspendMode);
SetupStore("AllowSuspend", m_NewSetup.AllowSuspend);
StreamdevServerSetup = m_NewSetup;
if (restart)
cStreamdevServer::Init();
}

41
server/setup.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* $Id: setup.h,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_SETUPSERVER_H
#define VDR_STREAMDEV_SETUPSERVER_H
#include "common.h"
struct cStreamdevServerSetup {
cStreamdevServerSetup(void);
bool SetupParse(const char *Name, const char *Value);
int MaxClients;
int StartVTPServer;
int VTPServerPort;
char VTPBindIP[20];
int StartHTTPServer;
int HTTPServerPort;
int HTTPStreamType;
char HTTPBindIP[20];
int SuspendMode;
int AllowSuspend;
};
extern cStreamdevServerSetup StreamdevServerSetup;
class cStreamdevServerMenuSetupPage: public cStreamdevMenuSetupPage {
private:
cStreamdevServerSetup m_NewSetup;
protected:
virtual void Store(void);
public:
cStreamdevServerMenuSetupPage(void);
virtual ~cStreamdevServerMenuSetupPage();
};
#endif // VDR_STREAMDEV_SETUPSERVER_H

99
server/streamer.c Normal file
View File

@@ -0,0 +1,99 @@
/*
* $Id: streamer.c,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
*/
#include <vdr/ringbuffer.h>
#include <vdr/device.h>
#include <sys/types.h>
#include <unistd.h>
#include "server/streamer.h"
#include "server/suspend.h"
#include "server/setup.h"
#include "tools/socket.h"
#include "common.h"
#define VIDEOBUFSIZE MEGABYTE(3)
#define MAXBLOCKSIZE TS_SIZE*10
cStreamdevStreamer::cStreamdevStreamer(const char *Name)
#if VDRVERSNUM >= 10300
:cThread("Streamdev: " + (cTBString)Name)
#endif
{
m_Active = false;
m_Receivers = 0;
m_Buffer = NULL;
m_Name = Name;
m_Socket = NULL;
m_RingBuffer = new cRingBufferLinear(VIDEOBUFSIZE, TS_SIZE * 2, true);
}
cStreamdevStreamer::~cStreamdevStreamer() {
Stop();
if (m_Buffer != NULL) delete[] m_Buffer;
delete m_RingBuffer;
}
void cStreamdevStreamer::Start(cTBSocket *Socket) {
m_Socket = Socket;
Attach();
if (!m_Active)
cThread::Start();
}
void cStreamdevStreamer::Stop(void) {
if (m_Active) {
Dprintf("stopping live streamer\n");
m_Active = false;
Cancel(3);
}
}
uchar *cStreamdevStreamer::Process(const uchar *Data, int &Count, int &Result) {
if (m_Buffer == NULL)
m_Buffer = new uchar[MAXBLOCKSIZE];
if (Count > MAXBLOCKSIZE)
Count = MAXBLOCKSIZE;
memcpy(m_Buffer, Data, Count);
Result = Count;
return m_Buffer;
}
void cStreamdevStreamer::Action(void) {
int max = 0;
#if VDRVERSNUM < 10300
isyslog("Streamdev: %s thread started (pid=%d)", m_Name, getpid());
#endif
m_Active = true;
while (m_Active) {
int recvd;
const uchar *block = m_RingBuffer->Get(recvd);
if (block && recvd > 0) {
int result = 0;
uchar *sendBlock = Process(block, recvd, result);
m_RingBuffer->Del(recvd);
if (result > max) max = result;
if (!m_Socket->TimedWrite(sendBlock, result, 150)) {
if (errno != ETIMEDOUT) {
esyslog("ERROR: Streamdev: Couldn't write data: %s", strerror(errno));
m_Active = false;
}
}
} else
usleep(1); // this keeps the CPU load low (XXX: waiting buffers)
}
Dprintf("Max. Transmit Blocksize was: %d\n", max);
#if VDRVERSNUM < 10300
isyslog("Streamdev: %s thread stopped", m_Name);
#endif
}

43
server/streamer.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* $Id: streamer.h,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_STREAMER_H
#define VDR_STREAMDEV_STREAMER_H
#include <vdr/thread.h>
#include <vdr/ringbuffer.h>
#include <vdr/tools.h>
class cTBSocket;
class cStreamdevStreamer: public cThread {
private:
bool m_Active;
int m_Receivers;
uchar *m_Buffer;
const char *m_Name;
cTBSocket *m_Socket;
cRingBufferLinear *m_RingBuffer;
protected:
virtual uchar *Process(const uchar *Data, int &Count, int &Result);
virtual void Action(void);
const cTBSocket *Socket(void) const { return m_Socket; }
public:
cStreamdevStreamer(const char *Name);
virtual ~cStreamdevStreamer();
virtual void Start(cTBSocket *Socket);
virtual void Stop(void);
int Put(uchar *Data, int Length) { return m_RingBuffer->Put(Data, Length); }
virtual void Detach(void) = 0;
virtual void Attach(void) = 0;
};
#endif // VDR_STREAMDEV_STREAMER_H

69
server/suspend.c Normal file
View File

@@ -0,0 +1,69 @@
/*
* $Id: suspend.c,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
*/
#include "server/suspend.h"
#include "server/suspend.dat"
#include "common.h"
cSuspendLive::cSuspendLive(void)
#if VDRVERSNUM >= 10300
: cThread("Streamdev: server suspend")
#endif
{
}
cSuspendLive::~cSuspendLive() {
Detach();
}
void cSuspendLive::Activate(bool On) {
Dprintf("Activate cSuspendLive %d\n", On);
if (On)
Start();
else
Stop();
}
void cSuspendLive::Stop(void) {
if (m_Active) {
m_Active = false;
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) {
DeviceStillPicture(suspend_mpg, sizeof(suspend_mpg));
usleep(100000);
}
#if VDRVERSNUM < 10300
isyslog("Streamdev: Suspend Live thread stopped");
#endif
}
bool cSuspendCtl::m_Active = false;
cSuspendCtl::cSuspendCtl(void):
cControl(m_Suspend = new cSuspendLive) {
m_Active = true;
}
cSuspendCtl::~cSuspendCtl() {
m_Active = false;
DELETENULL(m_Suspend);
}
eOSState cSuspendCtl::ProcessKey(eKeys Key) {
if (!m_Suspend->IsActive() || Key == kBack) {
DELETENULL(m_Suspend);
return osEnd;
}
return osContinue;
}

1206
server/suspend.dat Normal file

File diff suppressed because it is too large Load Diff

41
server/suspend.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* $Id: suspend.h,v 1.1 2004/12/30 22:44:26 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_SUSPEND_H
#define VDR_STREAMDEV_SUSPEND_H
#include <vdr/player.h>
class cSuspendLive: public cPlayer, cThread {
private:
bool m_Active;
protected:
virtual void Activate(bool On);
virtual void Action(void);
void Stop(void);
public:
cSuspendLive(void);
virtual ~cSuspendLive();
bool IsActive(void) const { return m_Active; }
};
class cSuspendCtl: public cControl {
private:
cSuspendLive *m_Suspend;
static bool m_Active;
public:
cSuspendCtl(void);
virtual ~cSuspendCtl();
virtual void Hide(void) {}
virtual eOSState ProcessKey(eKeys Key);
static bool IsActive(void) { return m_Active; }
};
#endif // VDR_STREAMDEV_SUSPEND_H