mirror of
https://projects.vdr-developer.org/git/vdr-plugin-streamdev.git
synced 2023-10-10 19:16:51 +02:00
- 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 Modified Files: Tag: v0_4 CONTRIBUTORS HISTORY Makefile README common.c common.h i18n.c remux/extern.c remux/extern.h server/connection.c server/connection.h server/connectionHTTP.c server/connectionHTTP.h server/connectionIGMP.c server/connectionVTP.c server/livestreamer.c server/livestreamer.h server/menuHTTP.c server/setup.c server/setup.h server/streamer.c server/streamer.h streamdev/externremux.sh streamdev/streamdevhosts.conf
This commit is contained in:
parent
a43455f660
commit
fa578940f7
@ -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
|
||||
|
18
HISTORY
18
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)
|
||||
|
4
Makefile
4
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)
|
||||
|
185
README
185
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
|
||||
|
4
common.c
4
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 <vdr/channels.h>
|
||||
@ -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.";
|
||||
|
||||
|
4
common.h
4
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
|
||||
};
|
||||
|
14
i18n.c
14
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
|
||||
|
130
remux/extern.c
130
remux/extern.c
@ -1,14 +1,19 @@
|
||||
#include "remux/extern.h"
|
||||
#include "server/server.h"
|
||||
#include "server/connection.h"
|
||||
#include "server/streamer.h"
|
||||
#include <vdr/channels.h>
|
||||
#include <vdr/tools.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -5,6 +5,9 @@
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include <string>
|
||||
|
||||
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);
|
||||
|
@ -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);
|
||||
|
@ -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 <map>
|
||||
|
||||
typedef std::map<std::string,std::string> tStrStrMap;
|
||||
typedef std::pair<std::string,std::string> 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
|
||||
|
@ -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 <ctype.h>
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 <map>
|
||||
#include <tools/select.h>
|
||||
|
||||
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<std::string,std::string> 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);
|
||||
|
@ -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 <ctype.h>
|
||||
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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 ...
|
||||
|
@ -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);
|
||||
|
@ -205,8 +205,8 @@ std::string cHtmlChannelList::StreamTypeMenu()
|
||||
(std::string) "[<a href=\"/PES/" + self + "\">PES</a>] ");
|
||||
typeMenu += (streamType == stES ? (std::string) "[ES] " :
|
||||
(std::string) "[<a href=\"/ES/" + self + "\">ES</a>] ");
|
||||
typeMenu += (streamType == stExtern ? (std::string) "[Extern] " :
|
||||
(std::string) "[<a href=\"/Extern/" + self + "\">Extern</a>] ");
|
||||
typeMenu += (streamType == stEXT ? (std::string) "[EXT] " :
|
||||
(std::string) "[<a href=\"/EXT/" + self + "\">EXT</a>] ");
|
||||
return typeMenu;
|
||||
}
|
||||
|
||||
|
@ -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 <vdr/menuitems.h>
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 <vdr/ringbuffer.h>
|
||||
@ -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")),
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user