diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 0bce176..6afa95f 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -120,3 +120,6 @@ BBlack lhanisch for fixing a memory leak in cStreamdevPatFilter::GetPid + +carel + for helping to find a way to cleanly shutdown externremux with mencoder diff --git a/HISTORY b/HISTORY index ed21dbe..8a65ea8 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,24 @@ VDR Plugin 'streamdev' Revision History --------------------------------------- +2010-07-19: Version 0.4.0 + +- using SIGINT in externremux to kill mencoder works better than SIGTERM; + especially x264 still needs a SIGKILL sometimes +- added --remove-destination to cp commands installing plugins +- updated Italian translation (thanks to Diego Pierotto) +- config option "client may suspend" hidden if not applicable +- updated and enhanced README +- added support for HTTP method HEAD +- rewrite of externremux.sh, including support for various URL parameters, + logging and improved shutdown +- start externremux script in a separate process group +- changed HTTP URL path for externremux from EXTERN to EXT (suggested by + Rolf Ahrenberg) +- HTTP headers now have to be emitted by externremux script +- pass channel related information and URL parameters to externremux script + through environment +- implement CGI like interface for externremux script - dropped "Synchronize EPG" feature. Please use epgsync-plugin instead (available from http://vdr.schmirler.de) - fixed a memory leak in cStreamdevPatFilter::GetPid (thanks to lhanisch) diff --git a/Makefile b/Makefile index 3656324..98770e3 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.15.2.3 2009/09/30 10:02:26 schmirl Exp $ +# $Id: Makefile,v 1.15.2.4 2010/07/19 13:50:11 schmirl Exp $ # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. @@ -106,7 +106,7 @@ libvdr-$(PLUGIN)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtoo %.so: $(CXX) $(CXXFLAGS) -shared $^ -o $@ - @cp $@ $(LIBDIR)/$@.$(APIVERSION) + @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) dist: clean @-rm -rf $(TMPDIR)/$(ARCHIVE) diff --git a/README b/README index e64d8d1..25047a1 100644 --- a/README +++ b/README @@ -15,9 +15,9 @@ Contents: 1. Description 2. Installation -2.1 VDR 1.3.X and older -2.2 VDR 1.4.X and above -2.3 Updating from streamdev 0.3.x +2.1 Compatibility +2.2 Compiling +2.3 Updating 3. Usage 3.1 Usage HTTP server 3.2 Usage IGMP multicast server @@ -27,7 +27,8 @@ Contents: 4.1 Plugins for VDR-to-VDR clients 4.2 Plugins for Server 4.3 Alternatives -5. Known Problems +5. externremux.sh +6. Known Problems 1. Description: @@ -90,42 +91,52 @@ to your needs. The syntax is the same as for svdrphosts.conf, so please consult VDR's documentation on how to fill that file, if you can't do it on-the-fly. There's also a sample externremux.sh script in this directory. It is used by -streamdev's external remux feature. The sample script uses mencoder. Please -check the script for further information. You can specify a different script -location with the -r parameter. The VDR commandline would then include a +streamdev's external remux feature. The sample script uses mencoder by default. +Please check the script for further information. You can specify a different +script location with the -r parameter. The VDR commandline would then include a "-P 'streamdev-server -r /usr/local/bin/remux.sh'". Note the additional quotes, as otherwise -r will be passed to VDR and not to streamdev. -2.1 VDR 1.3.X and older: ------------------------- +2.1 Compatibility: +------------------ -This version is not compatible to VDR releases older than 1.4.0. You will -probably need one of the streamdev-0.3.x releases. +This version is compatible to VDR releases 1.4.x and 1.5 upto 1.5.8. If you are +running some older VDR release, streamdev-0.3.4 is your friend. Check higher +streamdev releases if you are running a more recent VDR. -2.2 VDR 1.4.X and above: ------------------------- +2.2 Compiling: +-------------- -cd vdr-1.X.X/PLUGINS/src -tar xvfz vdr-streamdev-0.4.0.tgz -ln -s streamdev-0.4.0 streamdev -cp -r streamdev/streamdev VDRCONFDIR/plugins/ -cd ../.. -make [options, if necessary] vdr -make [options, if necessary] plugins + cd vdr-1.X.X/PLUGINS/src + tar xvfz vdr-streamdev-0.4.0.tgz + ln -s streamdev-0.4.0 streamdev + cp -r streamdev/streamdev-server VDRCONFDIR/plugins/ + cd ../.. + make [options, if necessary] vdr + make [options, if necessary] plugins -2.3 Updating from streamdev 0.3.x ----------------------------------- +To build only the plugin, change into the streamdev source folder and issue + make + +2.3 Updating: +-------------- + +If you are updating streamdev from an earlier release, you might have to +perform some additional steps. Check which version you've been running before, +then read below for the necessary changes. + +* Location of files: +-------------------- +(Affected: 0.3.x) Starting with streamdev 0.4.0, all additional files are kept in a directory called "streamdev" inside VDR's plugin config directory. It is the new default -location of externremux.sh and the new place where streamdev-server expects the -file "streamdevhosts.conf". You will have to move this file to its new location: +location of externremux.sh and the new place where streamdev-server expects +the file "streamdevhosts.conf". You will have to move this file to its new +location: -mv VDRCONFDIR/plugins/streamdevhosts.conf VDRCONFDIR/plugins/streamdev/ - -(Directory VDRCONFDIR/plugins/streamdev already exists, as you copied the -whole folder from the sources directory as suggested above, right?) + mv VDRCONFDIR/plugins/streamdevhosts.conf VDRCONFDIR/plugins/streamdev/ Now check the contents of streamdevhosts.conf. Does it contain a "0.0.0.0/0" entry? If your VDR machine is connected to the Internet, this line gives @@ -133,6 +144,22 @@ entry? If your VDR machine is connected to the Internet, this line gives prevent this (e.g. firewall). You might want to remove this line and enable HTTP authentication instead. +* Handling of externremux script: +--------------------------------- +(Affected: 0.3.x, 0.4.0pre) + +Streamdev server's externremux script became responsible for emitting all HTTP +headers. A quick and dirty extension to your current script would be: + + echo -ne 'Content-type: video/mpeg\r\n' + echo -ne '\r\n' + +However I encourage you to try the new externremux.sh script shipped with the +streamdev source distribution. + +To emphasize the required change in externremux, the URL path for passing the +stream through externremux has changed from EXTERN to EXT. + 3. Usage: --------- @@ -170,15 +197,15 @@ listen to with the parameter "HTTP Server Port". The parameter "HTTP Streamtype" allows you to specify a default stream type, which is used if no specific type has been requested in the URL (see below). The supported stream types are: -TS Transport Stream (i.e. a dump from the device) -PES Packetized Elemetary Stream (VDR's native recording format) -PS Program Stream (SVCD, DVD like stream) -ES Elementary Stream (only Video, if available, otherwise only Audio) -EXTERN Pass stream through external script (e.g. for converting with mencoder) + TS Transport Stream (i.e. a dump from the device) + PES Packetized Elemetary Stream (VDR's native recording format) + PS Program Stream (SVCD, DVD like stream) + ES Elementary Stream (only Video, if available, otherwise only Audio) + EXT Pass stream through external script (e.g. for converting with mencoder) Assuming that you leave the default port (3000), point your web browser to -http://hostname:3000/ + http://hostname:3000/ You will be presented a menu with links to various channel lists, including M3U playlist formats. @@ -186,27 +213,28 @@ playlist formats. If you don't want to use the HTML menu or the M3U playlists, you can access the streams directly like this: -http://hostname:3000/3 -http://hostname:3000/S19.2E-0-12480-898 + http://hostname:3000/3 + http://hostname:3000/S19.2E-0-12480-898 The first one will deliver a channel by number on the server, the second one will request the channel by unique channel id. In addition, you can specify the desired stream type as a path to the channel. -http://hostname:3000/TS/3 -http://hostname:3000/PES/S19.2E-0-12480-898 + http://hostname:3000/TS/3 + http://hostname:3000/PES/S19.2E-0-12480-898 The first one would deliver the stream in TS, the second one in PES format. -Possible values are 'PES', 'TS', 'PS', 'ES' and 'EXTERN'. You need to specify +Possible values are 'PES', 'TS', 'PS', 'ES' and 'EXT'. You need to specify the ES format explicitly if you want to listen to radio channels. Play them back i.e. with mpg123. -mpg123 http://hostname:3000/ES/200 + mpg123 http://hostname:3000/ES/200 -With 'EXTERN' you can also add a parameter which is passed as argument to the -externremux script. - -http://hostname:3000/EXTERN;some_parameter/3 +With 'EXT' you can also add parameters which are passed as arguments to the +externremux script (e.g. http://hostname:3000/EXT;param1=value1;param2=value2/3) +Check your externremux.sh script for the parameters it understands. For details +on how to modify or write your own externremux.sh, please see the chapter upon +externremux.sh further down. If you want to access streamdev's HTTP server from the Internet, do *not* grant access for anyone by allowing any IP in "streamdevhosts.conf". Instead, pass the @@ -214,7 +242,7 @@ access for anyone by allowing any IP in "streamdevhosts.conf". Instead, pass the as argument. Clients with an IP not accepted by "streamdevhosts.conf" will then have to login. The VDR commandline will have to look like this: -vdr ... -P 'streamdev-server -a vdr:secret' ... + vdr ... -P 'streamdev-server -a vdr:secret' ... Note the single quotes, as otherwise "-a" will be passed to VDR and not to streamdev-server. The login ("vdr" in the example above) doesn't have to exist @@ -345,6 +373,21 @@ which streamdev will accept to tune. Setting the minimum priority to a higher value than the maximum, you will get two ranges: "up to maximum" and "minimum and above". +Note that streamdev-client acts similar to a DVB card. It is possible to receive +multiple channels simultaneously, but only from the same transponder. Just add +additional instances of streamdev-client and you will be able to receive as many +transponders at a time. The same trick allows a client to receive channels from +different servers. To create an additional instance, copy the streamdev-client +binary to a different name (e.g. streamdev-client2): + + cd VDRPLUGINLIBDIR + cp libvdr-streamdev-client.so.1.X.X libvdr-streamdev-client2.so.1.X.X + +Now add -Pstreamdev-client2 to the VDR commandline. In the VDR plugin setup +a second streamdev-client entry should show up. Both instances have to be +configured individually. + + 4. Other useful Plugins: ------------------------ @@ -387,7 +430,57 @@ With its networking option, xineliboutput provides an alternative to streamdev. You will get the picture of the server VDR, including its OSD. However you won't get independent clients, as they all share the same output. -5. Known Problems: +5. externremux.sh: +------------------ + +When selecting streamtype "EXT", the TS stream from VDR is passed through an +external program for further processing. By default a script installed at +VDRCONFDIR/plugins/streamdev/externremux.sh is expected, however you may +specify a different location as parameter -r to the streamdev-server plugin +(see chapter upon Installation above). + +The TS stream is passed to the script on stdin, the resulting stream is expected +on stdout. The following parameters are passed to the script in the environment: + +* Information on the channel: + REMUX_CHANNEL_ID VDR channel ID + REMUX_CHANNEL_NAME Channel name + REMUX_VTYPE Video type (2 for MPEG-2) + REMUX_VPID Video PID (undefined if audio only) + REMUX_PPID PCR PID (undefined if equal to VPID) + REMUX_TPID Teletext PID (undefined if not available) + REMUX_APID Space separated list of audio pids + REMUX_ALANG Space separated list of audio languages + REMUX_DPID Space separated list of dolby pids + REMUX_DLANG Space separated list of dolby languages + REMUX_SPID Space separated list of subtitle pids + REMUX_SLANG Space separated list of subtitle languages + REMUX_PARAM_* All (user supplied) parameters (e.g. REMUX_PARAM_x) + +* Information on the connection (CGI like) + REMOTE_ADDR Client IP + SERVER_NAME Local IP + SERVER_PORT Local port + SERVER_PROTOCOL Streamdev protocol (HTTP, VTP, IGMP) + SERVER_SOFTWARE Streamdev version + All HTTP headers converted to uppercase, '-' replaced by '_' (e.g. USER_AGENT) + +The script should perform the following steps (pseudocode): + + if (SERVER_PROTOCOL == HTTP) + write headers (including Content-Type) to STDOUT + write empty line to STDOUT + if (REQUEST_METHOD == HEAD) + exit + endif + endif + while (read STDIN) + remux to STDOUT + wend + + onSIGINT/SIGKILL: cleanup and exit + +6. Known Problems: ------------------ * In VDR-to-VDR setup, the availability of a channel is checked with a different diff --git a/common.c b/common.c index 9a7d2c5..52b8539 100644 --- a/common.c +++ b/common.c @@ -1,5 +1,5 @@ /* - * $Id: common.c,v 1.7.2.1 2009/09/18 10:41:11 schmirl Exp $ + * $Id: common.c,v 1.7.2.2 2010/07/19 13:50:11 schmirl Exp $ */ #include @@ -10,7 +10,7 @@ using namespace std; -const char *VERSION = "0.4.0-pre"; +const char *VERSION = "0.4.0-CVS"; const char cMenuEditIpItem::IpCharacters[] = "0123456789."; diff --git a/common.h b/common.h index 7d61fe5..9ad8c30 100644 --- a/common.h +++ b/common.h @@ -1,5 +1,5 @@ /* - * $Id: common.h,v 1.11.2.2 2009/09/30 10:02:26 schmirl Exp $ + * $Id: common.h,v 1.11.2.3 2010/07/19 13:50:11 schmirl Exp $ */ #ifndef VDR_STREAMDEV_COMMON_H @@ -37,7 +37,7 @@ enum eStreamType { stPES, stPS, stES, - stExtern, + stEXT, stTSPIDS, st_Count }; diff --git a/i18n.c b/i18n.c index 14860d6..0af4dfa 100644 --- a/i18n.c +++ b/i18n.c @@ -1,5 +1,5 @@ /* - * $Id: i18n.c,v 1.8.2.8 2010/06/08 05:56:14 schmirl Exp $ + * $Id: i18n.c,v 1.8.2.9 2010/07/19 13:50:11 schmirl Exp $ */ #include "i18n.h" @@ -360,7 +360,7 @@ const tI18nPhrase Phrases[] = { { "Minimum Priority", // English "Minimale Priorität", // Deutsch "", // Slovenski - "", // Italiano + "Priorità minima", // Italiano "", // Nederlands "", // Português "", // Français @@ -385,7 +385,7 @@ const tI18nPhrase Phrases[] = { { "Maximum Priority", // English "Maximale Priorität", // Deutsch "", // Slovenski - "", // Italiano + "Priorità massima", // Italiano "", // Nederlands "", // Português "", // Français @@ -735,7 +735,7 @@ const tI18nPhrase Phrases[] = { { "Multicast Streaming Server", // English "Multicast Streaming Server", // Deutsch "", // Slovenski - "", // Italiano + "Server trasmissione Multicast", // Italiano "", // Nederlands "", // Português "", // Français @@ -760,7 +760,7 @@ const tI18nPhrase Phrases[] = { { "Start IGMP Server", // English "IGMP Server starten", // Deutsch "", // Slovenski - "", // Italiano + "Avvia Server IGMP", // Italiano "", // Nederlands "", // Português "", // Français @@ -785,7 +785,7 @@ const tI18nPhrase Phrases[] = { { "Multicast Client Port", // English "Port des Multicast Clients", // Deutsch "", // Slovenski - "", // Italiano + "Porta Client Multicast", // Italiano "", // Nederlands "", // Português "", // Français @@ -810,7 +810,7 @@ const tI18nPhrase Phrases[] = { { "Multicast Streamtype", // English "Multicast Streamtyp", // Deutsch "", // Slovenski - "", // Italiano + "Tipo flusso Multicast", // Italiano "", // Nederlands "", // Português "", // Français diff --git a/remux/extern.c b/remux/extern.c index 3791d10..ce76883 100644 --- a/remux/extern.c +++ b/remux/extern.c @@ -1,14 +1,19 @@ #include "remux/extern.h" #include "server/server.h" +#include "server/connection.h" #include "server/streamer.h" +#include #include #include #include #include #include +#include namespace Streamdev { +#define MAXENV 63 + class cTSExt: public cThread { private: cRingBufferLinear *m_ResultBuffer; @@ -20,7 +25,7 @@ protected: virtual void Action(void); public: - cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter); + cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids); virtual ~cTSExt(); void Put(const uchar *Data, int Count); @@ -29,7 +34,7 @@ public: } // namespace Streamdev using namespace Streamdev; -cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter): +cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids): m_ResultBuffer(ResultBuffer), m_Active(false), m_Process(-1), @@ -62,6 +67,115 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter): if (m_Process == 0) { // child process + char *env[MAXENV + 1]; + int i = 0; + +#define ADDENV(x...) if (asprintf(&env[i++], x) < 0) i-- + + // add channel ID, name and pids to environment + ADDENV("REMUX_CHANNEL_ID=%s", *Channel->GetChannelID().ToString()); + ADDENV("REMUX_CHANNEL_NAME=%s", Channel->Name()); +#if APIVERSNUM >= 10701 + ADDENV("REMUX_VTYPE=%d", Channel->Vtype()); +#endif + if (Channel->Vpid()) + ADDENV("REMUX_VPID=%d", Channel->Vpid()); + if (Channel->Ppid() != Channel->Vpid()) + ADDENV("REMUX_PPID=%d", Channel->Ppid()); + if (Channel->Tpid()) + ADDENV("REMUX_TPID=%d", Channel->Tpid()); + + std::string buffer; + if (Apids && *Apids) { + for (const int *pid = Apids; *pid; pid++) + (buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : ""); + ADDENV("REMUX_APID=%s", buffer.c_str()); + + buffer.clear(); + for (const int *pid = Apids; *pid; pid++) { + int j; + for (j = 0; Channel->Apid(j) && Channel->Apid(j) != *pid; j++) + ; + (buffer += Channel->Alang(j)) += (*(pid + 1) ? " " : ""); + } + ADDENV("REMUX_ALANG=%s", buffer.c_str()); + } + + if (Dpids && *Dpids) { + buffer.clear(); + for (const int *pid = Dpids; *pid; pid++) + (buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : ""); + ADDENV("REMUX_DPID=%s", buffer.c_str()); + + buffer.clear(); + for (const int *pid = Dpids; *pid; pid++) { + int j; + for (j = 0; Channel->Dpid(j) && Channel->Dpid(j) != *pid; j++) + ; + (buffer += Channel->Dlang(j)) += (*(pid + 1) ? " " : ""); + } + ADDENV("REMUX_DLANG=%s", buffer.c_str()); + } + + if (Channel->Spid(0)) { + buffer.clear(); + for (const int *pid = Channel->Spids(); *pid; pid++) + (buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : ""); + ADDENV("REMUX_SPID=%s", buffer.c_str()); + + buffer.clear(); + for (int j = 0; Channel->Spid(j); j++) + (buffer += Channel->Slang(j)) += (Channel->Spid(j + 1) ? " " : ""); + ADDENV("REMUX_SLANG=%s", buffer.c_str()); + } + + if (Connection) { + // add vars for a CGI like interface + // the following vars are not implemented: + // REMOTE_HOST, REMOTE_IDENT, REMOTE_USER + // CONTENT_TYPE, CONTENT_LENGTH, + // SCRIPT_NAME, PATH_TRANSLATED, GATEWAY_INTERFACE + ADDENV("REMOTE_ADDR=%s", Connection->RemoteIp().c_str()); + ADDENV("SERVER_NAME=%s", Connection->LocalIp().c_str()); + ADDENV("SERVER_PORT=%d", Connection->LocalPort()); + ADDENV("SERVER_PROTOCOL=%s", Connection->Protocol()); + ADDENV("SERVER_SOFTWARE=%s", VERSION); + + for (tStrStrMap::const_iterator it = Connection->Headers().begin(); it != Connection->Headers().end(); it++) { + if (i >= MAXENV) { + esyslog("streamdev-server: Too many headers for externremux.sh"); + break; + } + ADDENV("%s=%s", it->first.c_str(), it->second.c_str()); + } + + // look for section parameters: /path;param1=value1;param2=value2/ + std::string::size_type begin, end; + std::string path = Connection->Headers().at("PATH_INFO"); + begin = path.find(';', 0); + begin = path.find_first_not_of(';', begin); + end = path.find_first_of(";/", begin); + while (begin != std::string::npos && path[begin] != '/') { + std::string param = path.substr(begin, end - begin); + std::string::size_type e = param.find('='); + + if (i >= MAXENV) { + esyslog("streamdev-server: Too many parameters for externremux.sh"); + break; + } + else if (e > 0 && e != std::string::npos) { + ADDENV("REMUX_PARAM_%s", param.c_str()); + } + else + esyslog("streamdev-server: Invalid externremux.sh parameter %s", param.c_str()); + + begin = path.find_first_not_of(';', end); + end = path.find_first_of(";/", begin); + } + } + + env[i] = NULL; + dup2(inpipe[0], STDIN_FILENO); close(inpipe[1]); dup2(outpipe[1], STDOUT_FILENO); @@ -71,9 +185,11 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter): for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++) close(i); //close all dup'ed filedescriptors - std::string cmd = std::string(opt_remux) + " " + Parameter; - if (execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL) == -1) { - esyslog("streamdev-server: externremux script '%s' execution failed: %m", cmd.c_str()); + if (setpgid(0, 0) == -1) + esyslog("streamdev-server: externremux setpgid failed: %m"); + + if (execle("/bin/sh", "sh", "-c", opt_remux, NULL, env) == -1) { + esyslog("streamdev-server: externremux script '%s' execution failed: %m", opt_remux); _exit(-1); } // should never be reached @@ -172,9 +288,9 @@ void cTSExt::Put(const uchar *Data, int Count) } } -cExternRemux::cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter): +cExternRemux::cExternRemux(const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids): m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)), - m_Remux(new cTSExt(m_ResultBuffer, Parameter)) + m_Remux(new cTSExt(m_ResultBuffer, Connection, Channel, Apids, Dpids)) { m_ResultBuffer->SetTimeouts(500, 100); } diff --git a/remux/extern.h b/remux/extern.h index ff4ddec..070e4f6 100644 --- a/remux/extern.h +++ b/remux/extern.h @@ -5,6 +5,9 @@ #include #include +class cChannel; +class cServerConnection; + namespace Streamdev { class cTSExt; @@ -15,7 +18,7 @@ private: cTSExt *m_Remux; public: - cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter); + cExternRemux(const cServerConnection *Connection, const cChannel *Channel, const int *APids, const int *Dpids); virtual ~cExternRemux(); int Put(const uchar *Data, int Count); diff --git a/server/connection.c b/server/connection.c index 059a63e..6bcbd1e 100644 --- a/server/connection.c +++ b/server/connection.c @@ -1,5 +1,5 @@ /* - * $Id: connection.c,v 1.10.2.2 2009/09/18 10:41:11 schmirl Exp $ + * $Id: connection.c,v 1.10.2.3 2010/07/19 13:50:14 schmirl Exp $ */ #include "server/connection.h" @@ -27,7 +27,7 @@ cServerConnection::~cServerConnection() { } -const cChannel* cServerConnection::ChannelFromString(const char *String, int *Apid) { +const cChannel* cServerConnection::ChannelFromString(const char *String, int *Apid, int *Dpid) { const cChannel *channel = NULL; char *string = strdup(String); char *ptr, *end; @@ -58,7 +58,8 @@ const cChannel* cServerConnection::ChannelFromString(const char *String, int *Ap } if (channel != NULL && apididx > 0) { - int apid = 0, index = 1; + int apid = 0, dpid = 0; + int index = 1; for (int i = 0; channel->Apid(i) != 0; ++i, ++index) { if (index == apididx) { @@ -70,7 +71,7 @@ const cChannel* cServerConnection::ChannelFromString(const char *String, int *Ap if (apid == 0) { for (int i = 0; channel->Dpid(i) != 0; ++i, ++index) { if (index == apididx) { - apid = channel->Dpid(i); + dpid = channel->Dpid(i); break; } } @@ -78,6 +79,8 @@ const cChannel* cServerConnection::ChannelFromString(const char *String, int *Ap if (Apid != NULL) *Apid = apid; + if (Dpid != NULL) + *Dpid = dpid; } free(string); diff --git a/server/connection.h b/server/connection.h index 7979e35..95881e7 100644 --- a/server/connection.h +++ b/server/connection.h @@ -1,5 +1,5 @@ /* - * $Id: connection.h,v 1.5.2.3 2009/09/18 10:41:11 schmirl Exp $ + * $Id: connection.h,v 1.5.2.4 2010/07/19 13:50:14 schmirl Exp $ */ #ifndef VDR_STREAMDEV_SERVER_CONNECTION_H @@ -8,6 +8,11 @@ #include "tools/socket.h" #include "common.h" +#include + +typedef std::map tStrStrMap; +typedef std::pair tStrStr; + class cChannel; class cDevice; @@ -28,6 +33,8 @@ private: uint m_WriteBytes; uint m_WriteIndex; + tStrStrMap m_Headers; + protected: /* Will be called when a command terminated by a newline has been received */ @@ -41,7 +48,10 @@ protected: virtual bool Respond(const char *Message, bool Last = true, ...); //__attribute__ ((format (printf, 2, 4))); - static const cChannel *ChannelFromString(const char *String, int *Apid = NULL); + /* Add a request header */ + void SetHeader(const char *Name, const char *Value, const char *Prefix = "") { m_Headers.insert(tStrStr(std::string(Prefix) + Name, Value)); } + + static const cChannel *ChannelFromString(const char *String, int *Apid = NULL, int *Dpid = NULL); public: /* If you derive, specify a short string such as HTTP for Protocol, which @@ -89,6 +99,12 @@ public: virtual void Detach(void) = 0; virtual void Attach(void) = 0; + + /* This connections protocol name */ + virtual const char* Protocol(void) const { return m_Protocol; } + + /* std::map with additional information */ + const tStrStrMap& Headers(void) const { return m_Headers; } }; inline bool cServerConnection::HasData(void) const diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c index fb275ff..b687c71 100644 --- a/server/connectionHTTP.c +++ b/server/connectionHTTP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionHTTP.c,v 1.13.2.2 2009/02/13 07:02:26 schmirl Exp $ + * $Id: connectionHTTP.c,v 1.13.2.3 2010/07/19 13:50:14 schmirl Exp $ */ #include @@ -13,13 +13,13 @@ cConnectionHTTP::cConnectionHTTP(void): cServerConnection("HTTP"), m_Status(hsRequest), m_LiveStreamer(NULL), - m_StreamerParameter(""), m_Channel(NULL), - m_Apid(0), m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType), m_ChannelList(NULL) { Dprintf("constructor hsRequest\n"); + m_Apid[0] = m_Apid[1] = 0; + m_Dpid[0] = m_Dpid[1] = 0; } cConnectionHTTP::~cConnectionHTTP() @@ -37,30 +37,64 @@ bool cConnectionHTTP::Command(char *Cmd) Dprintf("command %s\n", Cmd); switch (m_Status) { case hsRequest: - Dprintf("Request\n"); - m_Request = Cmd; - m_Status = hsHeaders; - return true; + // parse METHOD PATH[?QUERY] VERSION + { + char *p, *q, *v; + p = strchr(Cmd, ' '); + if (p) { + *p = 0; + v = strchr(++p, ' '); + if (v) { + *v = 0; + SetHeader("REQUEST_METHOD", Cmd); + q = strchr(p, '?'); + if (q) + *q = 0; + SetHeader("QUERY_STRING", q ? ++q : ""); + SetHeader("PATH_INFO", p); + m_Status = hsHeaders; + return true; + } + } + } + return false; case hsHeaders: if (*Cmd == '\0') { m_Status = hsBody; return ProcessRequest(); } - if (strncasecmp(Cmd, "Host:", 5) == 0) { - Dprintf("Host-Header\n"); - m_Host = (std::string) skipspace(Cmd + 5); - return true; + else if (isspace(*Cmd)) { + ; //TODO: multi-line header } - 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; + else { + // convert header name to CGI conventions: + // uppercase, '-' replaced with '_', prefix "HTTP_" + char *p; + for (p = Cmd; *p != 0 && *p != ':'; p++) { + if (*p == '-') + *p = '_'; + else + *p = toupper(*p); + } + if (*p == ':') { + *p = 0; + p = skipspace(++p); + // don't disclose Authorization header + if (strcmp(Cmd, "AUTHORIZATION") == 0) { + char *q; + for (q = p; *q != 0 && *q != ' '; q++) + *q = toupper(*q); + if (p != q) { + *q = 0; + SetHeader("AUTH_TYPE", p); + m_Authorization = (std::string) skipspace(++q); + } + } + else + SetHeader(Cmd, p, "HTTP_"); } } - Dprintf("header\n"); return true; default: // skip additional blank lines @@ -73,10 +107,31 @@ bool cConnectionHTTP::Command(char *Cmd) bool cConnectionHTTP::ProcessRequest(void) { + // keys for Headers() hash + const static std::string AUTH_TYPE("AUTH_TYPE"); + const static std::string REQUEST_METHOD("REQUEST_METHOD"); + const static std::string PATH_INFO("PATH_INFO"); + Dprintf("process\n"); - if (!StreamdevHosts.Acceptable(RemoteIpAddr())) - { - if (!opt_auth || m_Authorization.empty() || m_Authorization.compare(opt_auth) != 0) { + if (!StreamdevHosts.Acceptable(RemoteIpAddr())) { + bool authOk = opt_auth && !m_Authorization.empty(); + if (authOk) { + tStrStrMap::const_iterator it = Headers().find(AUTH_TYPE); + + if (it == Headers().end()) { + // no authorization header present + authOk = false; + } + else if (it->second.compare("BASIC") == 0) { + // basic auth + authOk &= m_Authorization.compare(opt_auth) == 0; + } + else { + // unsupported auth type + authOk = false; + } + } + if (!authOk) { isyslog("streamdev-server: HTTP authorization required"); DeferClose(); return Respond("HTTP/1.0 401 Authorization Required") @@ -84,28 +139,22 @@ bool cConnectionHTTP::ProcessRequest(void) && Respond(""); } } - if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) { - switch (m_Job) { - case hjListing: - if (m_ChannelList) - return Respond("%s", true, m_ChannelList->HttpHeader().c_str()); - break; - case hjTransfer: - if (m_Channel == NULL) { - DeferClose(); - return Respond("HTTP/1.0 404 not found"); - } - - m_LiveStreamer = new cStreamdevLiveStreamer(0, m_StreamerParameter); + if (Headers().at(REQUEST_METHOD).compare("GET") == 0 && ProcessURI(Headers().at(PATH_INFO))) { + if (m_ChannelList) + return Respond("%s", true, m_ChannelList->HttpHeader().c_str()); + else if (m_Channel != NULL) { cDevice *device = GetDevice(m_Channel, 0); if (device != NULL) { device->SwitchChannel(m_Channel, false); - if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid)) { + m_LiveStreamer = new cStreamdevLiveStreamer(0, this); + if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL)) { m_LiveStreamer->SetDevice(device); if (!SetDSCP()) LOG_ERROR_STR("unable to set DSCP sockopt"); - if (m_StreamType == stES && (m_Apid != 0 || ISRADIO(m_Channel))) { + if (m_StreamType == stEXT) { + return Respond("HTTP/1.0 200 OK"); + } else if (ISRADIO(m_Channel) || (m_StreamType == stES && (m_Apid[0] || m_Dpid[0]))) { return Respond("HTTP/1.0 200 OK") && Respond("Content-Type: audio/mpeg") && Respond("icy-name: %s", true, m_Channel->Name()) @@ -116,12 +165,46 @@ bool cConnectionHTTP::ProcessRequest(void) && Respond(""); } } + DELETENULL(m_LiveStreamer); } - DELETENULL(m_LiveStreamer); DeferClose(); return Respond("HTTP/1.0 409 Channel not available") && Respond(""); } + else { + DeferClose(); + return Respond("HTTP/1.0 404 not found") + && Respond(""); + } + } else if (Headers().at(REQUEST_METHOD).compare("HEAD") == 0 && ProcessURI(Headers().at(PATH_INFO))) { + DeferClose(); + if (m_ChannelList) + return Respond("%s", true, m_ChannelList->HttpHeader().c_str()); + else if (m_Channel != NULL) { + cDevice *device = GetDevice(m_Channel, 0); + if (device != NULL) { + if (m_StreamType == stEXT) { + // TODO + return Respond("HTTP/1.0 200 OK") + && Respond(""); + } else if (ISRADIO(m_Channel) || (m_StreamType == stES && (m_Apid[0] || m_Dpid[0]))) { + return Respond("HTTP/1.0 200 OK") + && Respond("Content-Type: audio/mpeg") + && Respond("icy-name: %s", true, m_Channel->Name()) + && Respond(""); + } else { + return Respond("HTTP/1.0 200 OK") + && Respond("Content-Type: video/mpeg") + && Respond(""); + } + } + return Respond("HTTP/1.0 409 Channel not available") + && Respond(""); + } + else { + return Respond("HTTP/1.0 404 not found") + && Respond(""); + } } DeferClose(); @@ -136,78 +219,108 @@ void cConnectionHTTP::Flushed(void) if (m_Status != hsBody) return; - switch (m_Job) { - case hjListing: - if (m_ChannelList) { - if (m_ChannelList->HasNext()) { - if (!Respond("%s", true, m_ChannelList->Next().c_str())) - DeferClose(); - } - else { - DELETENULL(m_ChannelList); - m_Status = hsFinished; + if (m_ChannelList) { + if (m_ChannelList->HasNext()) { + if (!Respond("%s", true, m_ChannelList->Next().c_str())) DeferClose(); - } - return; } - // should never be reached - esyslog("streamdev-server cConnectionHTTP::Flushed(): no channel list"); - m_Status = hsFinished; - break; - - case hjTransfer: + else { + DELETENULL(m_ChannelList); + m_Status = hsFinished; + DeferClose(); + } + return; + } + else if (m_Channel != NULL) { Dprintf("streamer start\n"); m_LiveStreamer->Start(this); m_Status = hsFinished; - break; + } + else { + // should never be reached + esyslog("streamdev-server cConnectionHTTP::Flushed(): no job to do"); + m_Status = hsFinished; } } -bool cConnectionHTTP::CmdGET(const std::string &Opts) +cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const { - const char *ptr, *sp, *pp, *fp, *xp, *qp, *ep; - const cChannel *chan; - int apid = 0; + // keys for Headers() hash + const static std::string QUERY_STRING("QUERY_STRING"); + const static std::string HOST("HTTP_HOST"); - ptr = Opts.c_str(); + const std::string query = Headers().at(QUERY_STRING); - // find begin of URL - sp = skipspace(ptr); - // find end of URL (\0 or first space character) - for (ep = sp; *ep && !isspace(*ep); ep++) - ; - // find begin of query string (first ?) - for (qp = sp; qp < ep && *qp != '?'; qp++) - ; - // find begin of filename (last /) - for (fp = qp; fp > sp && *fp != '/'; --fp) - ; - // find begin of section params (first ;) - for (pp = sp; pp < fp && *pp != ';'; pp++) - ; - // find filename extension (first .) - for (xp = fp; xp < qp && *xp != '.'; xp++) - ; - if (qp - xp > 5) // too long for a filename extension - xp = qp; + std::string groupTarget; + cChannelIterator *iterator = NULL; + + if (Filebase.compare("tree") == 0) { + const cChannel* c = NULL; + size_t groupIndex = query.find("group="); + if (groupIndex != std::string::npos) + c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6)); + iterator = new cListTree(c); + groupTarget = Filebase + Fileext; + } else if (Filebase.compare("groups") == 0) { + iterator = new cListGroups(); + groupTarget = (std::string) "group" + Fileext; + } else if (Filebase.compare("group") == 0) { + const cChannel* c = NULL; + size_t groupIndex = query.find("group="); + if (groupIndex != std::string::npos) + c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6)); + iterator = new cListGroup(c); + } else if (Filebase.compare("channels") == 0) { + iterator = new cListChannels(); + } else if (Filebase.compare("all") == 0 || + (Filebase.empty() && Fileext.empty())) { + iterator = new cListAll(); + } + + if (iterator) { + if (Filebase.empty() || Fileext.compare(".htm") == 0 || Fileext.compare(".html") == 0) { + std::string self = Filebase + Fileext; + if (!query.empty()) + self += '?' + query; + return new cHtmlChannelList(iterator, m_StreamType, self.c_str(), groupTarget.c_str()); + } else if (Fileext.compare(".m3u") == 0) { + std::string base; + tStrStrMap::const_iterator it = Headers().find(HOST); + if (it != Headers().end()) + base = "http://" + it->second + "/"; + else + base = (std::string) "http://" + LocalIp() + ":" + + (const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/"; + base += Path; + return new cM3uChannelList(iterator, base.c_str()); + } else { + delete iterator; + } + } + return NULL; +} + +bool cConnectionHTTP::ProcessURI(const std::string& PathInfo) +{ + std::string filespec, fileext; + size_t file_pos = PathInfo.rfind('/'); + + if (file_pos != std::string::npos) { + size_t ext_pos = PathInfo.rfind('.'); + // file basename with leading / stripped off + filespec = PathInfo.substr(file_pos + 1, ext_pos - file_pos - 1); + if (ext_pos != std::string::npos) + // file extension including leading . + fileext = PathInfo.substr(ext_pos); + } + if (fileext.length() > 5) { + //probably not an extension + filespec += fileext; + fileext.clear(); + } - std::string type, filespec, fileext, query; // Streamtype with leading / stripped off - if (pp > sp) - type = Opts.substr(sp - ptr + 1, pp - sp - 1); - // Section parameters with leading ; stripped off - if (fp > pp) - m_StreamerParameter = Opts.substr(pp - ptr + 1, fp - pp - 1); - // file basename with leading / stripped off - if (xp > fp) - filespec = Opts.substr(fp - ptr + 1, xp - fp - 1); - // file extension including leading . - fileext = Opts.substr(xp - ptr, qp - xp); - // query string including leading ? - query = Opts.substr(qp - ptr, ep - qp); - - Dprintf("before channelfromstring: type(%s) param(%s) filespec(%s) fileext(%s) query(%s)\n", type.c_str(), m_StreamerParameter.c_str(), filespec.c_str(), fileext.c_str(), query.c_str()); - + std::string type = PathInfo.substr(1, PathInfo.find_first_of("/;", 1) - 1); const char* pType = type.c_str(); if (strcasecmp(pType, "PS") == 0) { m_StreamType = stPS; @@ -217,80 +330,19 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts) m_StreamType = stTS; } else if (strcasecmp(pType, "ES") == 0) { m_StreamType = stES; - } else if (strcasecmp(pType, "Extern") == 0) { - m_StreamType = stExtern; + } else if (strcasecmp(pType, "EXT") == 0) { + m_StreamType = stEXT; } - std::string groupTarget; - cChannelIterator *iterator = NULL; + Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.c_str()); - if (filespec.compare("tree") == 0) { - const cChannel* c = NULL; - size_t groupIndex = query.find("group="); - if (groupIndex != std::string::npos) - c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6)); - iterator = new cListTree(c); - groupTarget = filespec + fileext; - } else if (filespec.compare("groups") == 0) { - iterator = new cListGroups(); - groupTarget = (std::string) "group" + fileext; - } else if (filespec.compare("group") == 0) { - const cChannel* c = NULL; - size_t groupIndex = query.find("group="); - if (groupIndex != std::string::npos) - c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6)); - iterator = new cListGroup(c); - } else if (filespec.compare("channels") == 0) { - iterator = new cListChannels(); - } else if (filespec.compare("all") == 0 || - (filespec.empty() && fileext.empty())) { - iterator = new cListAll(); - } - - if (iterator) { - if (filespec.empty() || fileext.compare(".htm") == 0 || fileext.compare(".html") == 0) { - m_ChannelList = new cHtmlChannelList(iterator, m_StreamType, (filespec + fileext + query).c_str(), groupTarget.c_str()); - m_Job = hjListing; - } else if (fileext.compare(".m3u") == 0) { - std::string base; - if (*(m_Host.c_str())) - base = "http://" + m_Host + "/"; - else - base = (std::string) "http://" + LocalIp() + ":" + - (const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/"; - if (type.empty()) - { - switch (m_StreamType) - { - case stTS: base += "TS/"; break; - case stPS: base += "PS/"; break; - case stPES: base += "PES/"; break; - case stES: base += "ES/"; break; - case stExtern: base += "Extern/"; break; - default: break; - - } - } else { - base += type; - if (!m_StreamerParameter.empty()) - base += ";" + m_StreamerParameter; - base += "/"; - } - m_ChannelList = new cM3uChannelList(iterator, base.c_str()); - m_Job = hjListing; - } else { - delete iterator; - return false; - } - } else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) { - m_Channel = chan; - m_Apid = apid; - Dprintf("Apid is %d\n", apid); - m_Job = hjTransfer; + if ((m_ChannelList = ChannelListFromString(PathInfo.substr(0, file_pos), filespec.c_str(), fileext.c_str())) != NULL) { + Dprintf("Channel list requested\n"); + return true; + } else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) { + Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]); + return true; } else return false; - - Dprintf("after channelfromstring\n"); - return true; } diff --git a/server/connectionHTTP.h b/server/connectionHTTP.h index d7aa22b..c67272e 100644 --- a/server/connectionHTTP.h +++ b/server/connectionHTTP.h @@ -1,5 +1,5 @@ /* - * $Id: connectionHTTP.h,v 1.5.2.1 2008/10/14 11:05:59 schmirl Exp $ + * $Id: connectionHTTP.h,v 1.5.2.2 2010/07/19 13:50:14 schmirl Exp $ */ #ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H @@ -8,6 +8,7 @@ #include "connection.h" #include "server/livestreamer.h" +#include #include class cChannel; @@ -23,26 +24,19 @@ private: hsFinished, }; - enum eHTTPJob { - hjTransfer, - hjListing, - }; - - std::string m_Request; - std::string m_Host; std::string m_Authorization; - //std::map m_Headers; TODO: later? eHTTPStatus m_Status; - eHTTPJob m_Job; // job: transfer cStreamdevLiveStreamer *m_LiveStreamer; - std::string m_StreamerParameter; const cChannel *m_Channel; - int m_Apid; + int m_Apid[2]; + int m_Dpid[2]; eStreamType m_StreamType; // job: listing cChannelList *m_ChannelList; + cChannelList* ChannelListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const; + bool ProcessURI(const std::string &PathInfo); protected: bool ProcessRequest(void); @@ -56,7 +50,6 @@ public: virtual bool CanAuthenticate(void); virtual bool Command(char *Cmd); - bool CmdGET(const std::string &Opts); virtual bool Abort(void) const; virtual void Flushed(void); diff --git a/server/connectionIGMP.c b/server/connectionIGMP.c index b579754..4a2484e 100644 --- a/server/connectionIGMP.c +++ b/server/connectionIGMP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionIGMP.c,v 1.1.2.2 2009/02/13 10:39:42 schmirl Exp $ + * $Id: connectionIGMP.c,v 1.1.2.3 2010/07/19 13:50:14 schmirl Exp $ */ #include @@ -31,7 +31,7 @@ bool cConnectionIGMP::Start(cChannel *Channel, in_addr_t Dst) struct in_addr ip; ip.s_addr = Dst; if (Connect(inet_ntoa(ip), m_ClientPort)) { - m_LiveStreamer = new cStreamdevLiveStreamer(0); + m_LiveStreamer = new cStreamdevLiveStreamer(0, this); if (m_LiveStreamer->SetChannel(Channel, m_StreamType)) { m_LiveStreamer->SetDevice(device); if (!SetDSCP()) diff --git a/server/connectionVTP.c b/server/connectionVTP.c index 804c6f4..91d9265 100644 --- a/server/connectionVTP.c +++ b/server/connectionVTP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionVTP.c,v 1.18.2.6 2010/01/29 12:02:44 schmirl Exp $ + * $Id: connectionVTP.c,v 1.18.2.7 2010/07/19 13:50:14 schmirl Exp $ */ #include "server/connectionVTP.h" @@ -861,8 +861,8 @@ bool cConnectionVTP::CmdCAPS(char *Opts) return Respond(220, "Capability \"%s\" accepted", Opts); } - if (strcasecmp(Opts, "EXTERN") == 0) { - m_StreamType = stExtern; + if (strcasecmp(Opts, "EXT") == 0) { + m_StreamType = stEXT; return Respond(220, "Capability \"%s\" accepted", Opts); } @@ -1058,7 +1058,7 @@ bool cConnectionVTP::CmdTUNE(char *Opts) return Respond(560, "Channel not available"); delete m_LiveStreamer; - m_LiveStreamer = new cStreamdevLiveStreamer(1); + m_LiveStreamer = new cStreamdevLiveStreamer(1, this); m_LiveStreamer->SetChannel(chan, m_StreamType); m_LiveStreamer->SetDevice(dev); if(m_LiveSocket) diff --git a/server/livestreamer.c b/server/livestreamer.c index c894468..ff4f708 100644 --- a/server/livestreamer.c +++ b/server/livestreamer.c @@ -343,10 +343,9 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i // --- cStreamdevLiveStreamer ------------------------------------------------- -cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Parameter): - cStreamdevStreamer("streamdev-livestreaming"), +cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, const cServerConnection *Connection): + cStreamdevStreamer("streamdev-livestreaming", Connection), m_Priority(Priority), - m_Parameter(Parameter), m_NumPids(0), m_StreamType(stTSPIDS), m_Channel(NULL), @@ -460,40 +459,38 @@ void cStreamdevLiveStreamer::StartReceiver(void) } } -bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid) +bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType StreamType, const int* Apid, const int *Dpid) { Dprintf("Initializing Remuxer for full channel transfer\n"); //printf("ca pid: %d\n", Channel->Ca()); m_Channel = Channel; m_StreamType = StreamType; - int apid[2] = { Apid, 0 }; - const int *Apids = Apid ? apid : m_Channel->Apids(); - const int *Dpids = Apid ? NULL : m_Channel->Dpids(); + const int *Apids = Apid ? Apid : m_Channel->Apids(); + const int *Dpids = Dpid ? Dpid : m_Channel->Dpids(); switch (m_StreamType) { case stES: { int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid(); - if (Apid != 0) - pid = Apid; + if (Apid && Apid[0]) + pid = Apid[0]; + else if (Dpid && Dpid[0]) + pid = Dpid[0]; m_Remux = new cTS2ESRemux(pid); return SetPids(pid); } case stPES: - m_Remux = new cTS2PESRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), - m_Channel->Spids()); + m_Remux = new cTS2PESRemux(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); case stPS: - m_Remux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), - m_Channel->Spids()); + m_Remux = new cTS2PSRemux(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); - case stExtern: - m_Remux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), - m_Channel->Spids(), m_Parameter); + case stEXT: + m_Remux = new cExternRemux(Connection(), m_Channel, Apids, Dpids); // fall through case stTS: // This should never happen, but ... diff --git a/server/livestreamer.h b/server/livestreamer.h index 92448bb..283a395 100644 --- a/server/livestreamer.h +++ b/server/livestreamer.h @@ -18,7 +18,6 @@ class cStreamdevLiveReceiver; class cStreamdevLiveStreamer: public cStreamdevStreamer { private: int m_Priority; - std::string m_Parameter; int m_Pids[MAXRECEIVEPIDS + 1]; int m_NumPids; eStreamType m_StreamType; @@ -32,13 +31,13 @@ private: bool HasPid(int Pid); public: - cStreamdevLiveStreamer(int Priority, std::string Parameter = ""); + cStreamdevLiveStreamer(int Priority, const cServerConnection *Connection); virtual ~cStreamdevLiveStreamer(); void SetDevice(cDevice *Device) { m_Device = Device; } bool SetPid(int Pid, bool On); bool SetPids(int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL); - bool SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid = 0); + bool SetChannel(const cChannel *Channel, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL); virtual int Put(const uchar *Data, int Count); virtual uchar *Get(int &Count); diff --git a/server/menuHTTP.c b/server/menuHTTP.c index 7277473..47df3a8 100644 --- a/server/menuHTTP.c +++ b/server/menuHTTP.c @@ -205,8 +205,8 @@ std::string cHtmlChannelList::StreamTypeMenu() (std::string) "[PES] "); typeMenu += (streamType == stES ? (std::string) "[ES] " : (std::string) "[ES] "); - typeMenu += (streamType == stExtern ? (std::string) "[Extern] " : - (std::string) "[Extern] "); + typeMenu += (streamType == stEXT ? (std::string) "[EXT] " : + (std::string) "[EXT] "); return typeMenu; } diff --git a/server/setup.c b/server/setup.c index 3c3db1c..a35d38d 100644 --- a/server/setup.c +++ b/server/setup.c @@ -1,5 +1,5 @@ /* - * $Id: setup.c,v 1.3.2.3 2009/10/13 06:38:58 schmirl Exp $ + * $Id: setup.c,v 1.3.2.4 2010/07/19 13:50:14 schmirl Exp $ */ #include @@ -51,7 +51,7 @@ const char* cStreamdevServerMenuSetupPage::StreamTypes[st_Count - 1] = { "PES", "PS", "ES", - "Extern" + "EXT" }; const char* cStreamdevServerMenuSetupPage::SuspendModes[sm_Count] = { @@ -63,15 +63,25 @@ const char* cStreamdevServerMenuSetupPage::SuspendModes[sm_Count] = { cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) { m_NewSetup = StreamdevServerSetup; + Set(); +} + +cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() { +} + +void cStreamdevServerMenuSetupPage::Set(void) { static const char* modes[sm_Count]; for (int i = 0; i < sm_Count; i++) modes[i] = tr(SuspendModes[i]); + int current = Current(); + Clear(); AddCategory (tr("Common Settings")); Add(new cMenuEditIntItem (tr("Maximum Number of Clients"), &m_NewSetup.MaxClients, 0, 100)); Add(new cMenuEditStraItem(tr("Suspend behaviour"), &m_NewSetup.SuspendMode, sm_Count, modes)); - Add(new cMenuEditBoolItem(tr("Client may suspend"), &m_NewSetup.AllowSuspend)); + if (m_NewSetup.SuspendMode == smOffer) + Add(new cMenuEditBoolItem(tr("Client may suspend"), &m_NewSetup.AllowSuspend)); AddCategory (tr("VDR-to-VDR Server")); Add(new cMenuEditBoolItem(tr("Start VDR-to-VDR Server"), &m_NewSetup.StartVTPServer)); @@ -88,10 +98,8 @@ cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) { Add(new cMenuEditIntItem (tr("Multicast Client Port"), &m_NewSetup.IGMPClientPort, 0, 65535)); Add(new cMenuEditStraItem(tr("Multicast Streamtype"), &m_NewSetup.IGMPStreamType, st_Count - 1, StreamTypes)); Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.IGMPBindIP)); - SetCurrent(Get(1)); -} - -cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() { + SetCurrent(Get(current)); + Display(); } void cStreamdevServerMenuSetupPage::AddCategory(const char *Title) { @@ -140,3 +148,10 @@ void cStreamdevServerMenuSetupPage::Store(void) { cStreamdevServer::Initialize(); } +eOSState cStreamdevServerMenuSetupPage::ProcessKey(eKeys Key) { + int oldMode = m_NewSetup.SuspendMode; + eOSState state = cMenuSetupPage::ProcessKey(Key); + if (oldMode != m_NewSetup.SuspendMode) + Set(); + return state; +} diff --git a/server/setup.h b/server/setup.h index 06c2e67..0bc3b13 100644 --- a/server/setup.h +++ b/server/setup.h @@ -1,5 +1,5 @@ /* - * $Id: setup.h,v 1.1.1.1.2.2 2009/09/18 10:41:12 schmirl Exp $ + * $Id: setup.h,v 1.1.1.1.2.3 2010/07/19 13:50:14 schmirl Exp $ */ #ifndef VDR_STREAMDEV_SETUPSERVER_H @@ -37,8 +37,10 @@ private: cStreamdevServerSetup m_NewSetup; void AddCategory(const char *Title); + void Set(); protected: virtual void Store(void); + virtual eOSState ProcessKey(eKeys Key); public: cStreamdevServerMenuSetupPage(void); diff --git a/server/streamer.c b/server/streamer.c index 97e4ab6..a10c1f4 100644 --- a/server/streamer.c +++ b/server/streamer.c @@ -1,5 +1,5 @@ /* - * $Id: streamer.c,v 1.16.2.3 2009/06/29 06:25:30 schmirl Exp $ + * $Id: streamer.c,v 1.16.2.4 2010/07/19 13:50:14 schmirl Exp $ */ #include @@ -100,8 +100,9 @@ void cStreamdevWriter::Action(void) // --- cStreamdevStreamer ----------------------------------------------------- -cStreamdevStreamer::cStreamdevStreamer(const char *Name): +cStreamdevStreamer::cStreamdevStreamer(const char *Name, const cServerConnection *Connection): cThread(Name), + m_Connection(Connection), m_Writer(NULL), m_RingBuffer(new cStreamdevBuffer(STREAMERBUFSIZE, TS_SIZE * 2, true, "streamdev-streamer")), diff --git a/server/streamer.h b/server/streamer.h index acb5486..ec09b0f 100644 --- a/server/streamer.h +++ b/server/streamer.h @@ -1,5 +1,5 @@ /* - * $Id: streamer.h,v 1.8.2.3 2009/06/29 06:25:30 schmirl Exp $ + * $Id: streamer.h,v 1.8.2.4 2010/07/19 13:50:14 schmirl Exp $ */ #ifndef VDR_STREAMDEV_STREAMER_H @@ -11,6 +11,7 @@ class cTBSocket; class cStreamdevStreamer; +class cServerConnection; #ifndef TS_SIZE #define TS_SIZE 188 @@ -64,6 +65,7 @@ public: class cStreamdevStreamer: public cThread { private: + const cServerConnection *m_Connection; cStreamdevWriter *m_Writer; cStreamdevBuffer *m_RingBuffer; cStreamdevBuffer *m_SendBuffer; @@ -74,9 +76,11 @@ protected: bool IsRunning(void) const { return m_Writer; } public: - cStreamdevStreamer(const char *Name); + cStreamdevStreamer(const char *Name, const cServerConnection *Connection = NULL); virtual ~cStreamdevStreamer(); + const cServerConnection* Connection(void) const { return m_Connection; } + virtual void Start(cTBSocket *Socket); virtual void Stop(void); bool Abort(void); diff --git a/streamdev/externremux.sh b/streamdev/externremux.sh index e2b4156..f198382 100755 --- a/streamdev/externremux.sh +++ b/streamdev/externremux.sh @@ -1,48 +1,267 @@ -#!/bin/sh +#!/bin/bash # # externremux.sh - sample remux script using mencoder for remuxing. # # Install this script as VDRCONFDIR/plugins/streamdev/externremux.sh # -# The parameter STREAMQUALITY selects the default remux parameters. Adjust -# to your needs and point your web browser to http://servername:3000/extern/ -# To select different remux parameters on the fly, insert a semicolon and -# the name of the requested quality: http://servername:3000/extern;WLAN11/ +# The parameter QUALITY selects the default remux parameters. Adjust +# to your needs and point your web browser to http://servername:3000/ext/ +# To select different remux parameters on the fly, insert a semicolon +# followed by the name and value of the requested parameter, e.g: +# e.g. http://servername:3000/ext;QUALITY=WLAN11;VBR=512/ +# The following parameters are recognized: +# +# PROG actual remux program +# VC video codec +# VBR video bitrate (kbit) +# VOPTS custom video options +# WIDTH scale video to width +# AC audio codec +# ABR audio bitrate (kbit) +# AOPTS custom audio options +# -# CONFIG START - STREAMQUALITY="DSL6000" # DSL{1,2,3,6}000, LAN10, WLAN{11,54}, IPAQ - TMP=/tmp/externremux-${RANDOM:-$$} - MENCODER=mencoder -# CONFIG END +########################################################################## -mkdir -p $TMP -mkfifo $TMP/out.avi -(trap "rm -rf $TMP" EXIT HUP INT TERM ABRT; cat $TMP/out.avi) & +### GENERAL CONFIG START +### +# Pick one of DSL1000/DSL2000/DSL3000/DSL6000/DSL16000/LAN10/WLAN11/WLAN54 +QUALITY='DSL1000' +# Program used for logging (logging disabled if empty) +LOGGER=logger +# Path and name of FIFO +FIFO=/tmp/externremux-${RANDOM:-$$} +# Default remux program (cat/mencoder/ogg) +PROG=mencoder +# Use mono if $ABR is lower than this value +ABR_MONO=64 +### +### GENERAL CONFIG END -case ${1:-$STREAMQUALITY} in - DSL1000) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=100 \ - -oac mp3lame -lameopts preset=15:mode=3 -vf scale=160:104 \ - -o $TMP/out.avi -- - &>$TMP/out.log ;; - DSL2000) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=128 \ - -oac mp3lame -lameopts preset=15:mode=3 -vf scale=160:104 \ - -o $TMP/out.avi -- - &>$TMP/out.log ;; - DSL3000) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=250 \ - -oac mp3lame -lameopts preset=15:mode=3 -vf scale=320:208 \ - -o $TMP/out.avi -- - &>$TMP/out.log ;; - DSL6000) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=350 \ - -oac mp3lame -lameopts preset=15:mode=3 -vf scale=320:208 \ - -o $TMP/out.avi -- - &>$TMP/out.log ;; - LAN10) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=4096 \ - -oac mp3lame -lameopts preset=standard \ - -o $TMP/out.avi -- - &>$TMP/out.log ;; - WLAN11) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=768 \ - -oac mp3lame -lameopts preset=standard -vf scale=640:408 \ - -o $TMP/out.avi -- - &>$TMP/out.log ;; - WLAN54) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=2048 \ - -oac mp3lame -lameopts preset=standard \ - -o $TMP/out.avi -- - &>$TMP/out.log ;; - IPAQ) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=350 \ - -oac mp3lame -lameopts preset=15:mode=3 -vf scale=320:208 \ - -o $TMP/out.avi -- - &>$TMP/out.log ;; - *) touch $TMP/out.avi ;; +### MENCODER CONFIG START +### +# mencoder binary +MENCODER=mencoder +# Default video codec (e.g. lavc/x264/copy) +MENCODER_VC=lavc +# Default audio codec (e.g. lavc/mp3lame/faac/copy) +MENCODER_AC=mp3lame +# Default video codec if lavc is used (-ovc lavc -lavcopts vcodec=) +MENCODER_LAVC_VC=mpeg4 +# Default audio codec if lavc is used (-oac lavc -lavcopts acodec=) +MENCODER_LAVC_AC=mp2 +### +### MENCODER CONFIG END + +### OGG CONFIG START +### +# ffmpeg2theora binary +OGG=ffmpeg2theora +# speedlevel - lower value gives better quality but is slower (0..2) +OGG_SPEED=1 +# videoquality - higher value gives better quality but is slower (0..10) +OGG_VQUALITY=0 +# audioquality - higher value gives better quality but is slower (0..10) +OGG_AQUALITY=0 +### +### OGG CONFIG END + +########################################################################## + +function hasOpt { echo "$1" | grep -q "\b${2}\b"; } + +function isNumeric() { echo "$@" | grep -q '^[0-9]\{1,\}$'; } + +function remux_cat +{ + startReply + exec 3<&0 + cat 0<&3 >"$FIFO" & +} + +function remux_mencoder +{ + # lavc may be used for video and audio + LAVCOPTS=() + + # Assemble video options + VC=${REMUX_PARAM_VC:-$MENCODER_VC} + VOPTS=${REMUX_PARAM_VOPTS} + WIDTH=${REMUX_PARAM_WIDTH:-$WIDTH} + case "$VC" in + lavc) + LAVCOPTS=( + ${VOPTS} + $(hasOpt "$VOPTS" vcodec || echo "vcodec=$MENCODER_LAVC_VC") + ${VBR:+vbitrate=$VBR} + ) + [ ${#LAVCOPTS[*]} -gt 0 ] && VOPTS=$(IFS=:; echo -lavcopts "${LAVCOPTS[*]}") + ;; + x264) + X264OPTS=( + ${VOPTS} + $(hasOpt "$VOPTS" threads || echo "threads=auto") + ${VBR:+bitrate=$ABR} + ) + [ ${#X264OPTS[*]} -gt 0 ] && VOPTS=$(IFS=:; echo -x264encopts "${X264OPTS[*]}") + ;; + copy) + VOPTS= + ;; + *) + error "Unknown video codec '$VC'" + ;; + esac + + # Assemble audio options + AC=${REMUX_PARAM_AC:-$MENCODER_AC} + AOPTS=${REMUX_PARAM_AOPTS} + case "$AC" in + lavc) + LAVCOPTS=( + ${LAVCOPTS[*]} + ${AOPTS} + $(hasOpt "$AOPTS" acodec || echo "acodec=$MENCODER_LAVC_AC") + ${ABR:+abitrate=$ABR} + ) + + [ ${#LAVCOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -lavcopts "${LAVCOPTS[*]}") + # lavc used for video and audio decoding - wipe out VOPTS as video options became part of AOPTS + [ "$VC" = lavc ] && VOPTS= + ;; + mp3lame) + LAMEOPTS=( + ${AOPTS} + $(isNumeric "${ABR}" && [ "${ABR}" -lt "$ABR_MONO" ] && ! hasOpt "${AOPTS}" mode ] && echo 'mode=3') + ${ABR:+preset=$ABR} + ) + [ ${#LAMEOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -lameopts "${LAMEOPTS[*]}") + ;; + faac) + FAACOPTS=( + ${AOPTS} + ${ABR:+br=$ABR} + ) + [ ${#FAACOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -faacopts "${FAACOPTS[*]}") + ;; + copy) + AOPTS= + ;; + *) + error "Unknown audio codec '$AC'" + ;; + esac + + + startReply + exec 3<&0 + echo "$MENCODER" \ + -ovc $VC $VOPTS \ + -oac $AC $AOPTS \ + ${WIDTH:+-vf scale -zoom -xy $WIDTH} \ + -o "$FIFO" -- - >&2 + "$MENCODER" \ + -ovc $VC $VOPTS \ + -oac $AC $AOPTS \ + ${WIDTH:+-vf scale -zoom -xy $WIDTH} \ + -o "$FIFO" -- - 0<&3 >/dev/null & +} + +function remux_ogg +{ + VOPTS=${REMUX_PARAM_VOPTS//[:=]/ } + AOPTS=${REMUX_PARAM_AOPTS//[:=]/ } + WIDTH=${REMUX_PARAM_WIDTH:-$WIDTH} + + OGGOPTS=( + ${VOPTS} + ${VBR:+--videobitrate $VBR} + $(hasOpt "${VOPTS}" videoquality || echo "--videoquality $OGG_VQUALITY") + $(hasOpt "${VOPTS}" speedlevel || echo "--speedlevel $OGG_SPEED") + ${AOPTS} + ${ABR:+--audiobitrate $ABR} + $(isNumeric "${ABR}" && [ "${ABR}" -lt "$ABR_MONO" ] && ! hasOpt "${AOPTS}" channels ] && echo '--channels 1') + $(hasOpt "${AOPTS}" audioquality || echo "--audioquality $OGG_AQUALITY") + $(hasOpt "${AOPTS}" audiostream || echo '--audiostream 1') + ) + + startReply + exec 3<&0 + echo "$OGG" --format ts \ + ${OGGOPTS[*]} \ + ${WIDTH:+--width $WIDTH --height $(($WIDTH * 3 / 4 / 8 * 8))} \ + --title "VDR Streamdev: ${REMUX_CHANNEL_NAME}" \ + --output "$FIFO" -- - 0<&3 >&2 + "$OGG" --format ts \ + ${OGGOPTS[*]} \ + ${WIDTH:+--width $WIDTH --height $(($WIDTH * 3 / 4 / 8 * 8))} \ + --title "VDR Streamdev: ${REMUX_CHANNEL_NAME}" \ + --output "$FIFO" -- - 0<&3 >/dev/null & +} + +function error +{ + if [ "$SERVER_PROTOCOL" = HTTP ]; then + echo -ne "Content-type: text/plain\r\n" + echo -ne '\r\n' + echo "$*" + fi + + echo "$*" >&2 + exit 1 +} + +function startReply +{ + if [ "$SERVER_PROTOCOL" = HTTP ]; then + # send content-type and custom headers + echo -ne "Content-type: ${CONTENTTYPE}\r\n" + for header in "${HEADER[@]}"; do echo -ne "$header\r\n"; done + echo -ne '\r\n' + + # abort after headers + [ "$REQUEST_METHOD" = HEAD ] && exit 0 + fi + + # create FIFO and read from it in the background + mkfifo "$FIFO" + trap "trap '' EXIT HUP INT TERM ABRT PIPE CHLD; kill -INT 0; sleep 1; fuser -k '$FIFO'; rm '$FIFO'" EXIT HUP INT TERM ABRT PIPE CHLD + cat "$FIFO" <&- & +} + +HEADER=() + +[ "$LOGGER" ] && exec 2> >($LOGGER -t "vdr: [$$] ${0##*/}" 2>&-) + +# set default content-types +case "$REMUX_VPID" in + ''|0|1) CONTENTTYPE='audio/mpeg';; + *) CONTENTTYPE='video/mpeg';; esac + +QUALITY=${REMUX_PARAM_QUALITY:-$QUALITY} +case "$QUALITY" in + DSL1000|dsl1000) VBR=96; ABR=16; WIDTH=160;; + DSL2000|dsl2000) VBR=128; ABR=16; WIDTH=160;; + DSL3000|dsl3000) VBR=256; ABR=16; WIDTH=320;; + DSL6000|dsl6000) VBR=378; ABR=32; WIDTH=320;; + DSL16000|dsl16000) VBR=512; ABR=32; WIDTH=480;; + WLAN11|wlan11) VBR=768; ABR=64; WIDTH=640;; + WLAN45|wlan45) VBR=2048; ABR=128; WIDTH=;; + LAN10|lan10) VBR=4096; ABR=; WIDTH=;; + *) error "Unknown quality '$QUALITY'";; +esac +ABR=${REMUX_PARAM_ABR:-$ABR} +VBR=${REMUX_PARAM_VBR:-$VBR} +WIDTH=${REMUX_PARAM_WIDTH:-$WIDTH} +PROG=${REMUX_PARAM_PROG:-$PROG} + +case "$PROG" in + cat) remux_cat;; + mencoder) remux_mencoder;; + ogg) remux_ogg;; + *) error "Unknown remuxer '$PROG'";; +esac + +set -o monitor +wait diff --git a/streamdev/streamdevhosts.conf b/streamdev/streamdevhosts.conf index 49882a2..7243eeb 100644 --- a/streamdev/streamdevhosts.conf +++ b/streamdev/streamdevhosts.conf @@ -11,4 +11,4 @@ #192.168.100.0/24 # any host on the local net #204.152.189.113 # a specific host #239.255.0.0/16 # uncomment for IGMP multicast streaming -#0.0.0.0/0 # any host on any net (USE THIS WITH CARE!) +#0.0.0.0/0 # any host on any net (DON'T DO THAT! USE AUTHENTICATION)