62 Commits
master ... v0_4

Author SHA1 Message Date
Frank Schmirler
17c22dc7ee increased client side timeout for TUNE command 2011-01-27 16:17:07 +01:00
Frank Schmirler
fe9a58b88c fixed regression: no receiver created for ES/PS/PES
(reported by Gavin Hamill)
2010-12-10 16:38:10 +01:00
Frank Schmirler
635ccc479f Snapshot 2010-09-15 2011-03-24 19:20:05 +02:00
schmirl
db3274c046 don't use std::map.at(). It's not available in older libstdc++ version
Modified Files:
 Tag: v0_4
	CONTRIBUTORS HISTORY remux/extern.c server/connectionHTTP.c
2010-07-22 14:18:36 +00:00
schmirl
b2f30affa9 fixed extremux x264 using value of ABR for VBR 2010-07-22 06:30:36 +00:00
schmirl
6f3b081dd0 fixed wrong URL path in m3u playlists 2010-07-20 12:26:09 +00:00
schmirl
ab4fc57879 - set externremux.sh executable in distribution archive
- externremux quality value should be wlan54, not wlan45
2010-07-20 06:24:04 +00:00
schmirl
fa578940f7 - 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
2010-07-19 13:50:11 +00:00
schmirl
a43455f660 dropped "Synchronize EPG" feature
Modified Files:
 Tag: v0_4
	HISTORY README i18n.c streamdev-client.c streamdev-client.h
	client/device.c client/setup.c client/setup.h client/socket.c
	client/socket.h
2010-06-08 05:56:14 +00:00
schmirl
fc99a72467 fixed a memory leak in cStreamdevPatFilter::GetPid (thanks to lhanisch) 2010-02-20 23:05:20 +00:00
schmirl
ccbc738202 - length -1 is the correct value for streams in M3U playlists 2010-02-20 22:19:31 +00:00
schmirl
25f287f5b1 added DELT FORCE option to delete running timers (#554) 2010-01-29 12:02:44 +00:00
schmirl
913e6164b6 fixed missing virtual destructor for cTSRemux 2009-12-03 07:26:19 +00:00
schmirl
1eb9b85681 Capitalized languages 2009-11-03 11:20:04 +00:00
schmirl
98d20a98bb improved PARENTALRATING patch detection was missing in this branch 2009-10-13 06:48:23 +00:00
schmirl
824a192579 silenced warnings concerning asprintf (requested by Rolf Ahrenberg)
Modified Files:
 Tag: v0_4
	CONTRIBUTORS HISTORY server/connectionVTP.c server/setup.c
2009-10-13 06:38:58 +00:00
schmirl
900af77de7 don't update recordings list on CmdPLAY (reported by BBlack) 2009-09-30 10:10:53 +00:00
schmirl
e0f60bbd81 adapted and included xmbc patch for VDR 1.4.x
Modified Files:
 Tag: v0_4
	CONTRIBUTORS HISTORY Makefile common.h server/connectionVTP.c
	server/connectionVTP.h
Added Files:
 Tag: v0_4
	server/recplayer.c server/recplayer.h
2009-09-30 10:02:26 +00:00
schmirl
7ea2353728 - cleaned up common.h / common.c
- dropped cStreamdevMenuSetupPage
2009-09-18 10:41:11 +00:00
schmirl
7acdfe7428 Added defines for getting charset in VDR 1.5.3+ 2009-09-17 10:12:11 +00:00
schmirl
f4e9cc1de9 report charset in HTTP replies (suggested by Rolf Ahrenberg) 2009-09-15 10:39:09 +00:00
schmirl
d5f0744f4b use SO_KEEPALIVE option on all sockets do detect dead sockets 2009-09-04 13:24:34 +00:00
schmirl
22ff6d0801 Fixed sysctl command 2009-09-04 13:19:31 +00:00
schmirl
53a07a9dfa enable PatFilter for externremux, so VLC can be used as remuxer or client 2009-08-05 09:33:07 +00:00
schmirl
b099df3011 fixed insecure format strings in LSTX handlers (thanks to Anssi Hannula) 2009-07-17 06:25:55 +00:00
schmirl
06e1bb7976 updated Finish translation (thanks to Rolf Ahrenberg) 2009-07-07 10:50:13 +00:00
schmirl
47b4dc48fc removed redefinitions in includes - caused problems in older compilers 2009-07-06 06:23:36 +00:00
schmirl
458bb84ea7 fixed ts2ps.h defines 2009-07-06 06:14:14 +00:00
schmirl
cf1d2b9f6b fixed missing virtual for cTS2PESRemux destructor 2009-07-06 06:13:41 +00:00
schmirl
d7760f78fa silenced format mismatch warning on 64bit OS 2009-07-03 21:42:08 +00:00
schmirl
6feef574e9 file recplayer.h was added on branch v0_4 on 2009-09-30 10:02:27 +0000 2009-07-01 11:00:49 +00:00
schmirl
bc945caca2 file recplayer.c was added on branch v0_4 on 2009-09-30 10:02:27 +0000 2009-07-01 11:00:49 +00:00
schmirl
abb8e80033 now there's a common baseclass for all remuxers, make use of it
Modified Files:
 Tag: v0_4
	HISTORY remux/ts2pes.c remux/ts2pes.h remux/tsremux.h
	server/livestreamer.c server/livestreamer.h
2009-06-30 06:03:15 +00:00
schmirl
412c6982b6 - added namespace to remuxers
- increased WRITERBUFSIZE - buffer was too small for high bandwidth content
- removed cStreamdevStreamer::m_Running
- eliminated potential busy waits in remuxers
- updated cTSRemux static helpers to code of their VDR 1.6.0 counterparts
- use a copy of VDR 1.6.0's cRemux for TS to PES remuxing.
- make sure that only complete TS packets are written to ringbuffers
- use signaling instead of sleeps when writing to ringbuffers
- optimized cStreamdevPatFilter PAT packet initialization
- fixed cStreamdevPatFilter not processing PATs with length > TS_SIZE - 5
- use a small ringbuffer for cStreamdevPatFilter instead of writing to
  cStreamdevStreamers SendBuffer as two threads mustn't write to the same
  ringbuffer
Modified Files:
 Tag: v0_4
	CONTRIBUTORS HISTORY Makefile streamdev-server.c
	libdvbmpeg/transform.h remux/extern.c remux/extern.h
	remux/ts2es.c remux/ts2es.h remux/ts2ps.c remux/ts2ps.h
	remux/tsremux.c remux/tsremux.h server/livestreamer.c
	server/livestreamer.h server/streamer.c server/streamer.h
Added Files:
 Tag: v0_4
	remux/ts2pes.c remux/ts2pes.h
2009-06-29 06:25:27 +00:00
schmirl
cacd4b73d5 file ts2pes.h was added on branch v0_4 on 2009-06-29 06:25:28 +0000 2009-06-29 06:23:33 +00:00
schmirl
40fa22bba4 file ts2pes.c was added on branch v0_4 on 2009-06-29 06:25:28 +0000 2009-06-19 06:32:40 +00:00
schmirl
421a0e113a added comments to indicate that the VTP filter stream is proprietary forma
Modified Files:
 Tag: v0_4
	client/filter.c server/livefilter.c
2009-02-13 13:02:33 +00:00
schmirl
3bd1dc556f Typo 2009-02-13 12:31:03 +00:00
schmirl
e3599df308 Added IGMP Multicast server
Modified Files:
 Tag: v0_4
	CONTRIBUTORS HISTORY Makefile README i18n.c server/component.c
	server/component.h server/connection.c server/connection.h
	server/livefilter.c server/server.c server/setup.c
	server/setup.h server/streamer.c server/streamer.h
	streamdev/streamdevhosts.conf tools/socket.c tools/socket.h
Added Files:
 Tag: v0_4
	patches/vdr-cap_net_raw.diff server/componentIGMP.c
	server/componentIGMP.h server/connectionIGMP.c
	server/connectionIGMP.h
2009-02-13 10:39:40 +00:00
schmirl
fa06a6068b file connectionIGMP.h was added on branch v0_4 on 2009-02-13 10:39:42 +0000 2009-02-13 10:39:22 +00:00
schmirl
a0c4b3aa6d file connectionIGMP.c was added on branch v0_4 on 2009-02-13 10:39:42 +0000 2009-02-13 10:39:22 +00:00
schmirl
d14ae6829f file componentIGMP.h was added on branch v0_4 on 2009-02-13 10:39:42 +0000 2009-02-13 10:39:22 +00:00
schmirl
df27143a81 file componentIGMP.c was added on branch v0_4 on 2009-02-13 10:39:42 +0000 2009-02-13 10:39:22 +00:00
schmirl
486238595f file vdr-cap_net_raw.diff was added on branch v0_4 on 2009-02-13 10:39:41 +0000 2009-02-13 10:39:21 +00:00
schmirl
c000d1d50b ignore trailing blank lines in HTTP requests 2009-02-13 07:02:25 +00:00
schmirl
64ac6278bf Fixed parsing Min/MaxPriority from config 2009-02-03 10:26:23 +00:00
schmirl
507365d16e Updated Finnish translations 2009-02-02 11:53:05 +00:00
schmirl
f8002f7e31 Added min/max priority (#508)
Modified Files:
 Tag: v0_4
	HISTORY README i18n.c client/device.c client/setup.c
	client/setup.h
2009-01-29 07:49:04 +00:00
schmirl
46b8104cd2 added Network Media Tank browser support to HTML pages (#494) 2008-12-08 11:37:36 +00:00
schmirl
d716532d8c Compatiblity to Network Media Tank (#496)
- minor fixes of PAT repacker
- repack and send every PAT packet we receive
2008-11-24 12:10:29 +00:00
schmirl
6c620ea756 - fixed null pointer in server.c when cConnection::Accept() failes 2008-10-31 12:20:06 +00:00
schmirl
4d4f39f8cd consider Pids from channels.conf when HTTP TS streaming. Section filtering
is an optional feature for VDR devices, so we must not rely on the PMT
alone (#473)
2008-10-31 11:59:55 +00:00
schmirl
30ebb2dad1 Improved externremux script termination (#455) 2008-10-31 11:41:06 +00:00
schmirl
84f994384a - use cThread::Running()/Active() instead of private members
- replaced the last usleep by cCondWait
thanks to Rolf Ahrenberg (#383)
Modified Files:
 Tag: v0_4
	CONTRIBUTORS HISTORY server/server.c server/server.h
	server/streamer.c server/streamer.h server/suspend.c
	server/suspend.h
2008-10-22 11:59:35 +00:00
schmirl
5c24a13075 - fixed output format of some debug messages (thanks to Rolf Ahrenberg) 2008-10-22 11:17:25 +00:00
schmirl
52b4bfcd8c - added HTTP authentication (#475)
Modified Files:
 Tag: v0_4
	HISTORY README streamdev-server.c server/connection.h
	server/connectionHTTP.c server/connectionHTTP.h
	server/server.c server/server.h
2008-10-14 11:05:57 +00:00
schmirl
9258019e0f - added preprocessor directive for ancient gcc 2008-07-16 05:59:45 +00:00
schmirl
4e9c967872 - added Russian translation (thanks to Oleg Roitburd) 2008-06-26 14:17:10 +00:00
schmirl
90ae937018 - Fixed assignment of externremux.sh's default location
cPlugin::ConfigDirectory() cannot be used directly after the plugin has
been loaded. The return value of AddDirectory() must be allocated.
2008-04-29 07:00:57 +00:00
schmirl
9e46f86686 - added French translation (thanks to micky979) 2008-04-14 13:42:50 +00:00
schmirl
5788cd92b2 - updated Italian translation (thanks to Diego Pierotto)
- removed some unused translations
- added missing German translations
2008-04-14 07:12:34 +00:00
schmirl
c6c2344fef Applied and removed respect_ca patch 2008-04-07 15:07:38 +00:00
62 changed files with 6088 additions and 1283 deletions

View File

@@ -1,6 +1,10 @@
Special thanks go to the following persons (if you think your name is missing
here, please send an email to vdrdev@schmirler.de):
Klaus Schmidinger
for VDR as a whole
for permission to use VDR 1.6.0 cRemux code for PES remuxing
Sascha Volkenandt, the original author,
for this great plugin
@@ -8,14 +12,14 @@ The Metzler Brothers
as a lot of code has been taken from their libdvbmpeg package
Angelus (DOm)
for providing italian language texts
for providing Italian language texts
for reporting problems with the Elchi-Patch
Michal
for sending a patch to select the HTTP streamtype via remote
Rolf Ahrenberg
for providing finnish language texts
for providing Finnish language texts
for adding externremux.sh commandline parameter
for silencing compiler warnings
for adding PAT, PMT, PCR and EIT to HTTP TS streams
@@ -25,6 +29,12 @@ Rolf Ahrenberg
for a TS PAT repacker based on Petri Laine's VDR TS recording patch
for making it possible to pass parameters to externremux.sh
for removing pre VDR 1.4 legacy code
for fixing output format of some debug messages
for replacing private members by cThread::Running()/Active()
for improving externremux script termination
for fixing PAT repacker version field
for suggesting to include the charset in HTTP replies
for requesting replacement of asprintf calls
Rantanen Teemu
for providing vdr-incompletesections.diff
@@ -68,9 +78,67 @@ alexw
Olli Lammi
for fixing a busy wait when client isn't accepting data fast enough
for suggesting signaling instead of sleeping when writing to buffers
Joerg Pulz
for his FreeBSD compatibility patch
tobi
for pointing to unused files in the libdvbmpeg directory
Diego Pierotto
for providing Italian language texts
micky979
for providing French language texts
Anssi Hannula
for fixing insecure format strings in LSTX handlers
wirbel
for pointing out that section filtering is optional for VDR devices
Jori Hamalainen
for extensive testing while making stream compatible to Network Media Tank
for adding Network Media Tank browser support to HTML pages
owagner
for suggesting use of SO_KEEPALIVE socket option to detect dead sockets
Joachim König-Baltes
for fixing Min/MaxPriority parsing
Artem Makhutov
for suggesting and heavy testing IGMP based multicast streaming
Alwin Esch
for adding XBMC support by extending VTP capabilities
for adding the DELT FORCE option to delete running timers
BBlack
for reporting that updating recordings list on CmdPLAY is a bad idea
lhanisch
for fixing a memory leak in cStreamdevPatFilter::GetPid
carel
for helping to find a way to cleanly shutdown externremux with mencoder
wolfi.m
for reporting a typo in externremux quality parameter value
Norman Thiel
for reporting a wrong URL path in m3u playlists
vel_tins
for reporting that externremux x264 uses value of ABR for VBR
for various suggestions to improve externremux.sh
Matthias Prill
for reporting a compiler error with older libstdc++ versions
Timothy D. Lenz
for reporting missing support for invisible channel groups in HTTP menu
Gavin Hamill
for reporting that ES/PS/PES no longer works

122
HISTORY
View File

@@ -1,6 +1,128 @@
VDR Plugin 'streamdev' Revision History
---------------------------------------
- increased client side timeout for TUNE command
- fixed regression: no receiver created for ES/PS/PES (reported by Gavin
Hamill)
- VTP no longer uses a static priority value for its server-side receivers.
The server stores channel and priority requested with the PROV command and
re-uses these values in a subsequent TUNE for the same channel. The new
PRIO command is used to update the receiver's priority if necessary.
- added parameter HEIGHT to externremux.sh
- fixed syslog messages reporting local instead of remote IP and port
- log an error if externremux.sh is missing or not executable
- added dsyslog messages to help troubleshouting channel switch issues
- VTP command SUSP didn't attach the player to the primary device
- replacing a connections receiver is now an atomic operation. Solves
stuttering audio/video due to lost TS packets when adding/removing PIDs
- fixed missing support for invisible channel groups (groups without name)
in HTTP menu (reported by Timothy D. Lenz)
- don't quote actual program call in externremux.sh, so you can run th
program through e.g. nice or taskset just by extending the variable
which holds the program name
- in externremux.sh each mencoder audio and video codec has a dedicated
variable for a default option string now. Still you can override each
default option with an URL parameter
- externremux.sh mencoder now uses scale parameter with negative height
instead of -xy for scaling (suggested by vel_tins@vdrportal)
- added FPS (frames per second) parameter to externremux.sh (suggested by
vel_tins@vdrportal)
- don't use std::map.at(). It's not available in older libstdc++ version
(reported by Matthias Prill)
- fixed extremux x264 using value of ABR for VBR (thanks to vel_tins@vdrportal)
2010-07-20: Version 0.4.0b
- fixed wrong URL path in m3u playlists (reported by Norman Thiel)
2010-07-20: Version 0.4.0a
- set externremux.sh executable in distribution archive
- externremux quality value should be wlan54, not wlan45 (reported by
wolfi.m@vdrportal)
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)
- length -1 is the correct value for streams in M3U playlists
- added DELT FORCE option to delete running timers (thanks to Alwin Esch)
- fixed missing virtual destructor for cTSRemux
- improved PARENTALRATING patch detection was missing in this branch
- silenced warnings concerning asprintf (requested by Rolf Ahrenberg)
- don't update recordings list on CmdPLAY (reported by BBlack)
- adapted and included xmbc patch for VDR 1.4.x
- cleaned up common.h / common.c
- dropped cStreamdevMenuSetupPage
- report charset in HTTP replies (suggested by Rolf Ahrenberg)
- use SO_KEEPALIVE option on all sockets do detect dead sockets (thanks to
owagner)
- enable PatFilter for externremux, so VLC can be used as remuxer or client
- fixed insecure format strings in LSTX handlers (thanks to Anssi Hannula)
- updated Finish translation (thanks to Rolf Ahrenberg)
- removed redefinitions in includes - caused problems in older compilers
- fixed ts2ps.h defines
- fixed missing virtual for cTS2PESRemux destructor
- silenced format mismatch warning on 64bit OS
- now there's a common baseclass for all remuxers, make use of it
- added namespace to remuxers
- increased WRITERBUFSIZE - buffer was too small for high bandwidth content
- removed cStreamdevStreamer::m_Running
- eliminated potential busy waits in remuxers
- updated cTSRemux static helpers to code of their VDR 1.6.0 counterparts
- use a copy of VDR 1.6.0's cRemux for TS to PES remuxing.
- make sure that only complete TS packets are written to ringbuffers
- use signaling instead of sleeps when writing to ringbuffers
- optimized cStreamdevPatFilter PAT packet initialization
- fixed cStreamdevPatFilter not processing PATs with length > TS_SIZE - 5
- use a small ringbuffer for cStreamdevPatFilter instead of writing to
cStreamdevStreamers SendBuffer as two threads mustn't write to the same
ringbuffer
- added IGMP based multicast streaming
- ignore trailing blank lines in HTTP requests
- fixed parsing Min/MaxPriority from config (thanks to Joachim König-Baltes)
- updated Finnish translation (thanks to Rolf Ahrenberg)
- added Min/MaxPriority parameters. Can be used to keep client VDR from
using streamdev e.g. when recording
- added Network Media Tank browser support to HTML pages (thanks to Jori
Hamalainen)
- minor fixes of PAT repacker
- repack and send every PAT packet we receive
- fixed null pointer in server.c when cConnection::Accept() failes
- consider Pids from channels.conf when HTTP TS streaming. Section filtering
is an optional feature for VDR devices, so we must not rely on the PMT
alone (pointed out by wirbel@vdrportal)
- improved externremux script termination (thanks to Rolf Ahrenberg)
- use cThread::Running()/Active() instead of private members (thanks to
Rolf Ahrenberg)
- fixed output format of some debug messages (thanks to Rolf Ahrenberg)
- added HTTP authentication
- added preprocessor directive for ancient gcc
- added Russian translation (thanks to Oleg Roitburd)
- fixed assignment of externremux.sh's default location (reported by plautze)
- added French translation (thanks to micky979)
- updated Italian translation (thanks to Diego Pierotto)
- removed some unused translations
- added missing German translations
- applied and removed respect_ca patch
2008-04-07: Branched v0_4
- changed location of streamdevhosts.conf to VDRCONFDIR/plugins/streamdev

View File

@@ -1,7 +1,7 @@
#
# Makefile for a Video Disk Recorder plugin
#
# $Id: Makefile,v 1.15 2008/04/07 14:50:32 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.
@@ -57,12 +57,12 @@ CLIENTOBJS = $(PLUGIN)-client.o \
SERVEROBJS = $(PLUGIN)-server.o \
\
server/server.o server/connectionVTP.o server/connectionHTTP.o \
server/componentHTTP.o server/componentVTP.o server/connection.o \
server/component.o server/suspend.o server/setup.o server/streamer.o \
server/livestreamer.o server/livefilter.o server/menuHTTP.o \
\
remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o
server/server.o server/component.o server/connection.o \
server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \
server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \
server/streamer.o server/livestreamer.o server/livefilter.o \
server/suspend.o server/setup.o server/menuHTTP.o server/recplayer.o \
remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o
ifdef DEBUG
DEFINES += -DDEBUG
@@ -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)

283
README
View File

@@ -15,18 +15,20 @@ 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 VDR-to-VDR server
3.3 Usage VDR-to-VDR client
3.2 Usage IGMP multicast server
3.3 Usage VDR-to-VDR server
3.4 Usage VDR-to-VDR client
4. Other useful Plugins
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:
@@ -89,45 +91,74 @@ 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. This affects in
particular the file "streamdevhosts.conf". You will have to move it to its
new location:
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:
mv VDRCONFDIR/plugins/streamdevhosts.conf VDRCONFDIR/plugins/streamdev/
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?)
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
*anyone* full access to streamdev, unless you took some other measures to
prevent this (e.g. firewall). You might want to remove this line and enable
HTTP authentication instead.
The new default location for externremux.sh is also in this directory.
* 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:
---------
@@ -166,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.
@@ -182,29 +213,101 @@ 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.
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.
http://hostname:3000/EXTERN;some_parameter/3
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
"-a" commandline option to streamdev-server. It takes a username and a password
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:
3.2 Usage VDR-to-VDR server:
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
as a system account.
3.2 Usage IGMP multicast server:
--------------------------------
IGMP based multicast streaming is often used by settop boxes to receive IP TV.
Streamdev's multicast server allows you to feed live TV from VDR to such a
settop box. VLC is known to work well if you look for a software client.
The advantage of multicasting is that the actual stream is sent out only once,
regardless of how many clients want to receive it. The downside is, that you
cannot simply multicast across network boundaries. You need multicast routers.
For multicast streaming over the public Internet you would even need to register
for your own IP range. So don't even think of multicasting via Internet with
streamdev! Streamdev will send the stream only to one local ethernet segment and
all clients must be connected to this same segment. There must not be a router
inbetween. Also note that the client must not run on the streamdev-server
machine.
Each channel is offered on a different multicast IP. Channel 1 is available from
multicast IP 239.255.0.1, channel 2 from 239.255.0.2 and so on. The upper limit
is 239.255.254.255 which corresponds to channel 65279 (239.255.255.0/24 is
reserved according to RFC-2365).
Before you can use streamdev's multicast server, you might need to patch VDR.
Binding an IGMP socket is a privileged operation, so you must start VDR as root.
If you pass the -u option to VDR, it will drop almost all priviledges before
streamdev is even loaded. Apply vdr-cap_net_raw.diff to keep VDR from dropping
the CAP_NET_RAW capability required to bind the IGMP socket. The patch is part
of streamdev's source distribution. Check the patches subdirectory. There's no
need to patch VDR if it is kept running as root (not recommended).
The multicast server is disabled by default. Enter the streamdev-server setup
menu to enable it and - IMPORTANT - bind the multicast server to the IP of your
VDR server's LAN ethernet card. The multicast server will refuse to start with
the default bind adresse "0.0.0.0".
Now edit your streamdevhosts.conf. To allow streaming of all channels, it must
contain "239.255.0.0/16". Note that you cannot limit connections by client IP
here. You can however restrict which channels are allowed to be multicasted.
Enter individual multicast IPs instead of "239.255.0.0/16".
By default, the linux kernel will refuse to join more than 20 multicast groups.
You might want to increase this up to "number_of_channels + 1". Note that it's
"number_of_channels", not "maximum_channel_number".
#First 100 channels:
bash# sysctl -w net.ipv4.igmp_max_memberships=101
#All channels:
bash# COUNT=$(grep -c '^[^:]' PATH_TO_YOUR/channels.conf)
bash# sysctl -w net.ipv4.igmp_max_memberships=$COUNT
A multicast server never knows how many clients are actually receiving a stream.
If a client signals that it leaves a multicast group, the server has to query
for other listeners before it can stop the stream. This may delay zapping from
one transponder to an other. The client will probably requests the new channel
before the previous stream has been stopped. If there's no free DVB card, VDR
won't be able to fulfill the request until a DVB card becomes available and the
client resends the request.
3.3 Usage VDR-to-VDR server:
----------------------------
You can activate the VDR-to-VDR server part in the PlugIn's Setup Menu. It is
@@ -213,7 +316,7 @@ port where you want the server to listen for incoming connections. The server
will be activated when you push the OK button inside the setup menu, so there's
no need to restart VDR.
3.3 Usage VDR-to-VDR client:
3.4 Usage VDR-to-VDR client:
----------------------------
Streamdev-client adds a "Suspend Server" item to VDR's mainmenu. With the
@@ -260,14 +363,30 @@ With "Filter Streaming" enabled, the client will receive meta information like
EPG data and service information, just as if the client had its own DVB card.
Link channels and even a client-side EPG scan have been reported to work.
The last parameter, "Synchronize EPG", will have the client synchronize it's
program table with the server every now and then, but not regularly. This
happens when starting the client, and everytime VDR does its housekeeping
tasks. The only thing that's guaranteed is, that there will be a minimum
interval of ten seconds between each EPG synchronization. With "Filter
Streaming" this option has been obsoleted. If you still need to synchronize
EPG as additional information is available from the server, you should use the
epgsync-plugin instead (http://vdr.schmirler.de).
Finally with the maximum and minimum priority, you can keep VDR from considering
streamdev in certain cases. If for instance you have a streamdev client with its
own DVB card, VDR would normally use streamdev for recording. If this is not
what you want, you could set the maximum priority to 0. As recordings usually
have a much higher priority (default 50), streamdev is now no longer used for
recordings. The two parameters define the inclusive range of priorities for
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:
------------------------
@@ -311,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

View File

@@ -1,5 +1,5 @@
/*
* $Id: device.c,v 1.18 2008/04/07 14:50:32 schmirl Exp $
* $Id: device.c,v 1.18.2.4 2010/08/18 10:26:18 schmirl Exp $
*/
#include "client/device.h"
@@ -8,6 +8,7 @@
#include "tools/select.h"
#include <vdr/config.h>
#include <vdr/channels.h>
#include <vdr/ringbuffer.h>
#include <vdr/eit.h>
@@ -32,10 +33,8 @@ cStreamdevDevice::cStreamdevDevice(void) {
m_Device = this;
m_Pids = 0;
m_Priority = -1;
m_DvrClosed = true;
if (StreamdevClientSetup.SyncEPG)
ClientSocket.SynchronizeEPG();
}
cStreamdevDevice::~cStreamdevDevice() {
@@ -54,6 +53,12 @@ cStreamdevDevice::~cStreamdevDevice() {
DELETENULL(m_TSBuffer);
}
int cStreamdevDevice::ProvidesCa(const cChannel *Channel) const
{
// Encrypted is acceptable for now. Will ask the server later.
return Channel->Ca() <= CA_DVB_MAX ? cDevice::ProvidesCa(Channel) : 1;
}
bool cStreamdevDevice::ProvidesSource(int Source) const {
Dprintf("ProvidesSource, Source=%d\n", Source);
return true;
@@ -85,10 +90,23 @@ bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority,
Dprintf("ProvidesChannel, Channel=%s, Prio=%d\n", Channel->Name(), Priority);
if (StreamdevClientSetup.MinPriority <= StreamdevClientSetup.MaxPriority)
{
if (Priority < StreamdevClientSetup.MinPriority ||
Priority > StreamdevClientSetup.MaxPriority)
return false;
}
else
{
if (Priority < StreamdevClientSetup.MinPriority &&
Priority > StreamdevClientSetup.MaxPriority)
return false;
}
if (ClientSocket.DataSocket(siLive) != NULL
&& TRANSPONDER(Channel, m_Channel))
res = true;
else {
else if (ProvidesCa(Channel)) {
res = prio && ClientSocket.ProvidesChannel(Channel, Priority);
ndr = true;
}
@@ -103,6 +121,9 @@ bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel,
bool LiveView) {
Dprintf("SetChannelDevice Channel: %s, LiveView: %s\n", Channel->Name(),
LiveView ? "true" : "false");
LOCK_THREAD;
m_UpdatePriority = ClientSocket.SupportsPrio();
if (LiveView)
return false;
@@ -123,6 +144,8 @@ bool cStreamdevDevice::SetPid(cPidHandle *Handle, int Type, bool On) {
Handle->used);
LOCK_THREAD;
m_UpdatePriority = ClientSocket.SupportsPrio();
if (On && !m_TSBuffer) {
Dprintf("SetPid: no data connection -> OpenDvr()");
OpenDvrInt();
@@ -289,3 +312,16 @@ bool cStreamdevDevice::ReInit(void) {
return StreamdevClientSetup.StartClient ? Init() : true;
}
void cStreamdevDevice::UpdatePriority(void) {
if (m_Device) {
m_Device->Lock();
if (m_Device->m_UpdatePriority && ClientSocket.DataSocket(siLive)) {
int Priority = m_Device->Priority();
if (m_Device == cDevice::ActualDevice() && Priority < Setup.PrimaryLimit)
Priority = Setup.PrimaryLimit;
if (m_Device->m_Priority != Priority && ClientSocket.SetPriority(Priority))
m_Device->m_Priority = Priority;
}
m_Device->Unlock();
}
}

View File

@@ -1,5 +1,5 @@
/*
* $Id: device.h,v 1.7 2008/04/07 14:40:39 schmirl Exp $
* $Id: device.h,v 1.7.2.2 2010/08/18 10:26:18 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_DEVICE_H
@@ -15,13 +15,14 @@ class cTBString;
#define CMD_LOCK_OBJ(x) cMutexLock CmdLock((cMutex*)&(x)->m_Mutex)
class cStreamdevDevice: public cDevice {
friend class cRemoteRecordings;
private:
const cChannel *m_Channel;
cTSBuffer *m_TSBuffer;
cStreamdevFilters *m_Filters;
int m_Pids;
int m_Priority;
bool m_UpdatePriority;
bool m_DvrClosed;
static cStreamdevDevice *m_Device;
@@ -49,12 +50,14 @@ public:
cStreamdevDevice(void);
virtual ~cStreamdevDevice();
virtual int ProvidesCa(const cChannel *Channel) const;
virtual bool ProvidesSource(int Source) const;
virtual bool ProvidesTransponder(const cChannel *Channel) const;
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1,
bool *NeedsDetachReceivers = NULL) const;
virtual bool IsTunedToTransponder(const cChannel *Channel);
static void UpdatePriority(void);
static bool Init(void);
static bool ReInit(void);

View File

@@ -1,5 +1,5 @@
/*
* $Id: filter.c,v 1.12 2008/04/07 14:27:28 schmirl Exp $
* $Id: filter.c,v 1.12.2.1 2009/02/13 13:02:33 schmirl Exp $
*/
#include "client/filter.h"
@@ -226,6 +226,7 @@ void cStreamdevFilters::Action(void) {
u_short pid = (((u_short)block[1] & PID_MASK_HI) << 8) | block[2];
u_char tid = block[3];
bool Pusi = block[1] & 0x40;
// proprietary extension
int len = block[4];
#if 0
if (block[1] == 0xff &&

View File

@@ -1,5 +1,5 @@
/*
* $Id: setup.c,v 1.5 2008/04/07 14:50:32 schmirl Exp $
* $Id: setup.c,v 1.5.2.4 2010/06/08 05:56:15 schmirl Exp $
*/
#include <vdr/menuitems.h>
@@ -14,8 +14,9 @@ cStreamdevClientSetup::cStreamdevClientSetup(void) {
StartClient = false;
RemotePort = 2004;
StreamFilters = false;
SyncEPG = false;
HideMenuEntry = false;
MinPriority = -1;
MaxPriority = MAXPRIORITY;
strcpy(RemoteIp, "");
}
@@ -29,8 +30,9 @@ bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) {
}
else if (strcmp(Name, "RemotePort") == 0) RemotePort = atoi(Value);
else if (strcmp(Name, "StreamFilters") == 0) StreamFilters = atoi(Value);
else if (strcmp(Name, "SyncEPG") == 0) SyncEPG = atoi(Value);
else if (strcmp(Name, "HideMenuEntry") == 0) HideMenuEntry = atoi(Value);
else if (strcmp(Name, "MinPriority") == 0) MinPriority = atoi(Value);
else if (strcmp(Name, "MaxPriority") == 0) MaxPriority = atoi(Value);
else return false;
return true;
}
@@ -38,12 +40,13 @@ bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) {
cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(void) {
m_NewSetup = StreamdevClientSetup;
AddBoolEdit (tr("Hide Mainmenu Entry"),m_NewSetup.HideMenuEntry);
AddBoolEdit (tr("Start Client"), m_NewSetup.StartClient);
AddIpEdit (tr("Remote IP"), m_NewSetup.RemoteIp);
AddShortEdit(tr("Remote Port"), m_NewSetup.RemotePort);
AddBoolEdit (tr("Filter Streaming"), m_NewSetup.StreamFilters);
AddBoolEdit (tr("Synchronize EPG"), m_NewSetup.SyncEPG);
Add(new cMenuEditBoolItem(tr("Hide Mainmenu Entry"), &m_NewSetup.HideMenuEntry));
Add(new cMenuEditBoolItem(tr("Start Client"), &m_NewSetup.StartClient));
Add(new cMenuEditIpItem (tr("Remote IP"), m_NewSetup.RemoteIp));
Add(new cMenuEditIntItem (tr("Remote Port"), &m_NewSetup.RemotePort, 0, 65535));
Add(new cMenuEditBoolItem(tr("Filter Streaming"), &m_NewSetup.StreamFilters));
Add(new cMenuEditIntItem (tr("Minimum Priority"), &m_NewSetup.MinPriority, -1, MAXPRIORITY));
Add(new cMenuEditIntItem (tr("Maximum Priority"), &m_NewSetup.MaxPriority, -1, MAXPRIORITY));
SetCurrent(Get(0));
}
@@ -63,8 +66,9 @@ void cStreamdevClientMenuSetupPage::Store(void) {
SetupStore("RemoteIp", m_NewSetup.RemoteIp);
SetupStore("RemotePort", m_NewSetup.RemotePort);
SetupStore("StreamFilters", m_NewSetup.StreamFilters);
SetupStore("SyncEPG", m_NewSetup.SyncEPG);
SetupStore("HideMenuEntry", m_NewSetup.HideMenuEntry);
SetupStore("MinPriority", m_NewSetup.MinPriority);
SetupStore("MaxPriority", m_NewSetup.MaxPriority);
StreamdevClientSetup = m_NewSetup;

View File

@@ -1,5 +1,5 @@
/*
* $Id: setup.h,v 1.4 2008/04/07 14:50:32 schmirl Exp $
* $Id: setup.h,v 1.4.2.3 2010/06/08 05:56:15 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_SETUPCLIENT_H
@@ -16,13 +16,14 @@ struct cStreamdevClientSetup {
char RemoteIp[20];
int RemotePort;
int StreamFilters;
int SyncEPG;
int HideMenuEntry;
int MinPriority;
int MaxPriority;
};
extern cStreamdevClientSetup StreamdevClientSetup;
class cStreamdevClientMenuSetupPage: public cStreamdevMenuSetupPage {
class cStreamdevClientMenuSetupPage: public cMenuSetupPage {
private:
cStreamdevClientSetup m_NewSetup;

View File

@@ -1,5 +1,5 @@
/*
* $Id: socket.c,v 1.11 2008/04/07 14:40:40 schmirl Exp $
* $Id: socket.c,v 1.11.2.3 2010/08/18 10:26:18 schmirl Exp $
*/
#include <tools/select.h>
@@ -21,6 +21,7 @@ cClientSocket ClientSocket;
cClientSocket::cClientSocket(void)
{
memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count);
m_Prio = false;
Reset();
}
@@ -143,8 +144,14 @@ bool cClientSocket::CheckConnection(void) {
if(Command("CAPS FILTERS", 220))
Filters = ",FILTERS";
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s",
RemoteIp().c_str(), RemotePort(), Filters);
const char *Prio = "";
if(Command("CAPS PRIO", 220)) {
Prio = ",PRIO";
m_Prio = true;
}
isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s%s",
RemoteIp().c_str(), RemotePort(), Filters, Prio);
return true;
}
@@ -242,8 +249,8 @@ bool cClientSocket::SetChannelDevice(const cChannel *Channel) {
CMD_LOCK;
std::string command = (std::string)"TUNE "
+ (const char*)Channel->GetChannelID().ToString();
if (!Command(command, 220)) {
+ (const char*)Channel->GetChannelID().ToString();
if (!Command(command, 220, 10000)) {
if (errno == 0)
esyslog("ERROR: Streamdev: Couldn't tune %s:%d to channel %s",
RemoteIp().c_str(), RemotePort(), Channel->Name());
@@ -252,6 +259,21 @@ bool cClientSocket::SetChannelDevice(const cChannel *Channel) {
return true;
}
bool cClientSocket::SetPriority(int Priority) {
if (!CheckConnection()) return false;
CMD_LOCK;
std::string command = (std::string)"PRIO " + (const char*)itoa(Priority);
if (!Command(command, 220)) {
if (errno == 0)
esyslog("Streamdev: Failed to update priority on %s:%d", RemoteIp().c_str(),
RemotePort());
return false;
}
return true;
}
bool cClientSocket::SetPid(int Pid, bool On) {
if (!CheckConnection()) return false;
@@ -260,8 +282,8 @@ bool cClientSocket::SetPid(int Pid, bool On) {
std::string command = (std::string)(On ? "ADDP " : "DELP ") + (const char*)itoa(Pid);
if (!Command(command, 220)) {
if (errno == 0)
esyslog("Streamdev: Pid %d not available from %s:%d", Pid, LocalIp().c_str(),
LocalPort());
esyslog("Streamdev: Pid %d not available from %s:%d", Pid, RemoteIp().c_str(),
RemotePort());
return false;
}
return true;
@@ -277,7 +299,7 @@ bool cClientSocket::SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On) {
if (!Command(command, 220)) {
if (errno == 0)
esyslog("Streamdev: Filter %hu, %hhu, %hhu not available from %s:%d",
Pid, Tid, Mask, LocalIp().c_str(), LocalPort());
Pid, Tid, Mask, RemoteIp().c_str(), RemotePort());
return false;
}
return true;
@@ -301,52 +323,6 @@ bool cClientSocket::CloseDvr(void) {
return true;
}
bool cClientSocket::SynchronizeEPG(void) {
std::string buffer;
bool result;
FILE *epgfd;
if (!CheckConnection()) return false;
isyslog("Streamdev: Synchronizing EPG from server\n");
CMD_LOCK;
if (!Command("LSTE"))
return false;
if ((epgfd = tmpfile()) == NULL) {
esyslog("ERROR: Streamdev: Error while processing EPG data: %s",
strerror(errno));
return false;
}
while ((result = Expect(215, &buffer))) {
if (buffer[3] == ' ') break;
fputs(buffer.c_str() + 4, epgfd);
fputc('\n', epgfd);
}
if (!result) {
if (errno == 0)
esyslog("ERROR: Streamdev: Couldn't fetch EPG data from %s:%d",
RemoteIp().c_str(), RemotePort());
fclose(epgfd);
return false;
}
rewind(epgfd);
if (cSchedules::Read(epgfd))
cSchedules::Cleanup(true);
else {
esyslog("ERROR: Streamdev: Parsing EPG data failed");
fclose(epgfd);
return false;
}
fclose(epgfd);
return true;
}
bool cClientSocket::Quit(void) {
bool res;

View File

@@ -1,5 +1,5 @@
/*
* $Id: socket.h,v 1.6 2008/04/07 14:40:40 schmirl Exp $
* $Id: socket.h,v 1.6.2.2 2010/08/18 10:26:18 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_CLIENT_CONNECTION_H
@@ -20,6 +20,7 @@ private:
cTBSocket *m_DataSockets[si_Count];
cMutex m_Mutex;
char m_Buffer[BUFSIZ + 1]; // various uses
bool m_Prio; // server supports command PRIO
protected:
/* Send Command, and return true if the command results in Expected.
@@ -45,10 +46,11 @@ public:
bool CreateDataConnection(eSocketId Id);
bool CloseDataConnection(eSocketId Id);
bool SetChannelDevice(const cChannel *Channel);
bool SupportsPrio() { return m_Prio; }
bool SetPriority(int Priority);
bool SetPid(int Pid, bool On);
bool SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On);
bool CloseDvr(void);
bool SynchronizeEPG(void);
bool SuspendServer(void);
bool Quit(void);

140
common.c
View File

@@ -1,5 +1,5 @@
/*
* $Id: common.c,v 1.7 2008/04/07 14:27:27 schmirl Exp $
* $Id: common.c,v 1.7.2.2 2010/07/19 13:50:11 schmirl Exp $
*/
#include <vdr/channels.h>
@@ -7,146 +7,12 @@
#include "common.h"
#include "tools/select.h"
#include "i18n.h"
using namespace std;
const char *VERSION = "0.4.0-pre";
const char *VERSION = "0.4.0-CVS";
const char *StreamTypes[st_Count] = {
"TS",
"PES",
"PS",
"ES",
"Extern",
"", // used internally only
};
const char *SuspendModes[sm_Count] = {
"Offer suspend mode",
"Always suspended",
"Never suspended"
};
const char IpCharacters[] = "0123456789.";
char *GetNextLine(char *String, uint Length, uint &Offset) {
char *last, *first;
first = String + Offset;
for (last = first; last < String + Length; ++last) {
if (*last == '\012') {
if (*(last - 1) == '\015')
*(last - 1) = '\0';
*last++ = '\0';
Dprintf("IN: |%s|\n", first);
Offset = last - String;
return first;
}
}
return NULL;
}
const cChannel *ChannelFromString(const char *String, int *Apid) {
const cChannel *channel = NULL;
char *string = strdup(String);
char *ptr, *end;
int apididx = 0;
if ((ptr = strrchr(string, '+')) != NULL) {
*(ptr++) = '\0';
apididx = strtoul(ptr, &end, 10);
Dprintf("found apididx: %d\n", apididx);
}
if (isnumber(string)) {
int temp = strtol(String, NULL, 10);
if (temp >= 1 && temp <= Channels.MaxNumber())
channel = Channels.GetByNumber(temp);
} else {
channel = Channels.GetByChannelID(tChannelID::FromString(string));
if (channel == NULL) {
int i = 1;
while ((channel = Channels.GetByNumber(i, 1)) != NULL) {
if (String == channel->Name())
break;
i = channel->Number() + 1;
}
}
}
if (channel != NULL && apididx > 0) {
int apid = 0, index = 1;
for (int i = 0; channel->Apid(i) != 0; ++i, ++index) {
if (index == apididx) {
apid = channel->Apid(i);
break;
}
}
if (apid == 0) {
for (int i = 0; channel->Dpid(i) != 0; ++i, ++index) {
if (index == apididx) {
apid = channel->Dpid(i);
break;
}
}
}
if (Apid != NULL)
*Apid = apid;
}
free(string);
return channel;
}
void cStreamdevMenuSetupPage::AddCategory(const char *Title) {
char *buffer = NULL;
asprintf(&buffer, "--- %s -------------------------------------------------"
"---------------", Title );
cOsdItem *item = new cOsdItem(buffer);
free(buffer);
item->SetSelectable(false);
Add(item);
}
void cStreamdevMenuSetupPage::AddBoolEdit(const char *Title, int &Value) {
Add(new cMenuEditBoolItem(Title, &Value));
}
void cStreamdevMenuSetupPage::AddIpEdit(const char *Title, char *Value) {
Add(new cMenuEditIpItem(Title, Value));
}
void cStreamdevMenuSetupPage::AddShortEdit(const char *Title, int &Value) {
AddRangeEdit(Title, Value, 0, 65535);
}
void cStreamdevMenuSetupPage::AddRangeEdit(const char *Title, int &Value,
int Min, int Max) {
Add(new cMenuEditIntItem(Title, &Value, Min, Max));
}
void cStreamdevMenuSetupPage::AddSuspEdit(const char *Title, int &Value) {
static const char *SuspendModesTR[sm_Count] = { NULL };
if (SuspendModesTR[0] == NULL) {
for (int i = 0; i < sm_Count; ++i)
SuspendModesTR[i] = tr(SuspendModes[i]);
}
Add(new cMenuEditStraItem(Title, &Value, sm_Count, SuspendModesTR));
}
void cStreamdevMenuSetupPage::AddTypeEdit(const char *Title, int &Value) {
Add(new cMenuEditStraItem(Title, &Value, st_CountSetup, StreamTypes));
}
const char cMenuEditIpItem::IpCharacters[] = "0123456789.";
cMenuEditIpItem::cMenuEditIpItem(const char *Name, char *Value):
cMenuEditItem(Name) {

View File

@@ -1,5 +1,5 @@
/*
* $Id: common.h,v 1.11 2008/04/07 14:40:39 schmirl Exp $
* $Id: common.h,v 1.11.2.3 2010/07/19 13:50:11 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_COMMON_H
@@ -23,42 +23,23 @@
# define Dprintf(x...)
#endif
# define TRANSPONDER(c1, c2) (c1->Transponder() == c2->Transponder())
#define TRANSPONDER(c1, c2) (c1->Transponder() == c2->Transponder())
# define MAXPARSEBUFFER KILOBYTE(16)
#define MAXPARSEBUFFER KILOBYTE(16)
/* Check if a channel is a radio station. */
#define ISRADIO(x) ((x)->Vpid()==0||(x)->Vpid()==1||(x)->Vpid()==0x1fff)
class cChannel;
char *GetNextLine(char *String, uint Length, uint &Offset);
const cChannel *ChannelFromString(const char *String, int *Apid = NULL);
/* Disable logging if BUFCOUNT buffer overflows occur within BUFOVERTIME
milliseconds. Enable logging again if there is no error within BUFOVERTIME
milliseconds. */
#define BUFOVERTIME 5000
#define BUFOVERCOUNT 100
#define POLLFAIL esyslog("Streamdev: Polling failed: %s", strerror(errno))
#define WRITEFAIL esyslog("Streamdev: Writing failed: %s", strerror(errno))
#define READFAIL esyslog("Streamdev: Reading failed: %s", strerror(errno))
#define CHECKPOLL(x) if ((x)<0){POLLFAIL; return false;}
#define CHECKWRITE(x) if ((x)<0) { WRITEFAIL; return false; }
#define CHECKREAD(x) if ((x)<0) { READFAIL; return false; }
enum eStreamType {
stTS,
stPES,
stPS,
stES,
stExtern,
stEXT,
stTSPIDS,
#define st_CountSetup (stExtern+1)
#define st_Count (stTSPIDS+1)
st_Count
};
enum eSuspendMode {
@@ -72,29 +53,15 @@ enum eSocketId {
siLive,
siReplay,
siLiveFilter,
siDataRespond,
si_Count
};
extern const char *VERSION;
extern const char *StreamTypes[st_Count];
extern const char *SuspendModes[sm_Count];
extern const char IpCharacters[];
class cStreamdevMenuSetupPage: public cMenuSetupPage {
protected:
void AddCategory(const char *Title);
virtual void Store(void) = 0;
void AddBoolEdit(const char *Title, int &Value);
void AddIpEdit(const char *Title, char *Value);
void AddShortEdit(const char *Title, int &Value);
void AddRangeEdit(const char *Title, int &Value, int Min, int Max);
void AddSuspEdit(const char *Title, int &Value);
void AddTypeEdit(const char *Title, int &Value);
};
class cMenuEditIpItem: public cMenuEditItem {
private:
static const char IpCharacters[];
char *value;
int curNum;
int pos;

378
i18n.c
View File

@@ -1,5 +1,5 @@
/*
* $Id: i18n.c,v 1.8 2008/04/07 14:50:32 schmirl Exp $
* $Id: i18n.c,v 1.8.2.9 2010/07/19 13:50:11 schmirl Exp $
*/
#include "i18n.h"
@@ -10,10 +10,10 @@ const tI18nPhrase Phrases[] = {
{ "VDR Streaming Server", // English
"VDR Streaming Server", // Deutsch
"", // Slovenski
"", // Italiano
"Server trasmissione VDR", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Serveur de streaming VDR", // Français
"", // Norsk
"VDR-suoratoistopalvelin", // suomi
"", // Polski
@@ -23,7 +23,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"VDR Streaming áÕàÒÕà", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -35,10 +35,10 @@ const tI18nPhrase Phrases[] = {
{ "VTP Streaming Client", // English
"VTP Streaming Client", // Deutsch
"", // Slovenski
"", // Italiano
"Client trasmissione VTP", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Client de streaming VTP", // Français
"", // Norsk
"VTP-suoratoistoasiakas ", // suomi
"", // Polski
@@ -48,7 +48,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"VTP Streaming ÚÛØÕÝâ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -60,10 +60,10 @@ const tI18nPhrase Phrases[] = {
{ "Start VDR-to-VDR Server", // English
"VDR-zu-VDR Server starten", // Deutsch
"", // Slovenski
"Avvia il Server VDR-toVDR", // Italiano
"Avvia Server VDR-a-VDR", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Démarrer le serveur VDR-to-VDR", // Français
"", // Norsk
"Käynnistä VDR-palvelin", // suomi
"", // Polski
@@ -73,7 +73,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"ÁâÐàâ VDR-to-VDR áÕàÒÕà", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -85,10 +85,10 @@ const tI18nPhrase Phrases[] = {
{ "Start HTTP Server", // English
"HTTP Server starten", // Deutsch
"", // Slovenski
"Avvia il Server HTTP", // Italiano
"Avvia Server HTTP", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Démarrer le serveur HTTP", // Français
"", // Norsk
"Käynnistä HTTP-palvelin", // suomi
"", // Polski
@@ -98,7 +98,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"ÁâÐàâ HTTP áÕàÒÕàÐ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -110,10 +110,10 @@ const tI18nPhrase Phrases[] = {
{ "HTTP Streamtype", // English
"HTTP Streamtyp", // Deutsch
"", // Slovenski
"Tipo di Stream HTTP", // Italiano
"Tipo flusso HTTP", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Type de Streaming HTTP", // Français
"", // Norsk
"HTTP-lähetysmuoto", // Suomi
"", // Polski
@@ -123,7 +123,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"ÂØß HTTP ßÞâÞÚÐ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -135,10 +135,10 @@ const tI18nPhrase Phrases[] = {
{ "Start Client", // English
"Client starten", // Deutsch
"", // Slovenski
"Avvia il Client", // Italiano
"Avvia Client", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Démarrage du client", // Français
"", // Norsk
"Käynnistä VDR-asiakas", // suomi
"", // Polski
@@ -148,7 +148,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"ÁâÐàâ ÚÛØÕÝâÐ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -160,10 +160,10 @@ const tI18nPhrase Phrases[] = {
{ "VDR-to-VDR Server Port", // English
"Port des VDR-zu-VDR Servers", // Deutsch
"", // Slovenski
"Porta del Server VDR-to-VDR", // Italiano
"Porta Server VDR-a-VDR", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Port du serveur VDR-to-VDR", // Français
"", // Norsk
"VDR-palvelimen portti", // Suomi
"", // Polski
@@ -173,7 +173,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"VDR-to-VDR ßÞàâ áÕàÒÕàÐ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -185,10 +185,10 @@ const tI18nPhrase Phrases[] = {
{ "HTTP Server Port", // English
"Port des HTTP Servers", // Deutsch
"", // Slovenski
"Porta del Server HTTP", // Italiano
"Porta Server HTTP", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Port du serveur HTTP", // Français
"", // Norsk
"HTTP-palvelimen portti", // suomi
"", // Polski
@@ -198,7 +198,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"HTTP áÕàÒÕà ¿Þàâ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -210,10 +210,10 @@ const tI18nPhrase Phrases[] = {
{ "Maximum Number of Clients", // English
"Maximalanzahl an Clients", // Deutsch
"", // Slovenski
"Numero Massimo di Client", // Italiano
"Numero massimo di Client", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Nombre maximun de clients", // Français
"", // Norsk
"Suurin sallittu asiakkaiden määrä", // suomi
"", // Polski
@@ -223,7 +223,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"¼ÐÚá. ÚÞÛØçÕáâÒÞ ÚÛØÕÝâÞÒ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -238,7 +238,7 @@ const tI18nPhrase Phrases[] = {
"Indirizzo IP del Server", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Adresse IP du serveur", // Français
"", // Norsk
"Etäkoneen IP-osoite", // suomi
"", // Polski
@@ -248,7 +248,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"ÃÔÐÛÕÝÝëÙ IP", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -260,10 +260,10 @@ const tI18nPhrase Phrases[] = {
{ "Remote Port", // English
"Port der Gegenseite", // Deutsch
"", // Slovenski
"Porta del Server Remoto", // Italiano
"Porta Server Remoto", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Port du serveur", // Français
"", // Norsk
"Etäkoneen portti", // suomi
"", // Polski
@@ -273,32 +273,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
"", // Czech
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Remote Streamtype", // English
"Streamtyp von Gegenseite", // Deutsch
"", // Slovenski
"Tipo di Stream", // Italiano (oppure Flusso ?)
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"Etäkoneen lähetysmuoto", // suomi
"", // Polski
"", // Español
"", // Ellinika
"", // Svenska
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"ÃÔÐÛÕÝÝëÙ ßÞàâ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -310,10 +285,10 @@ const tI18nPhrase Phrases[] = {
{ "Common Settings", // English
"Allgemeines", // Deutsch
"", // Slovenski
"Settaggi Comuni", // Italiano
"Impostazioni comuni", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Paramètres communs", // Français
"", // Norsk
"Yleiset asetukset", // suomi
"", // Polski
@@ -323,7 +298,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"½ÐáâàÞÙÚØ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -335,10 +310,10 @@ const tI18nPhrase Phrases[] = {
{ "VDR-to-VDR Server", // English
"VDR-zu-VDR Server", // Deutsch
"", // Slovenski
"Server VDR-to-VDR", // Italiano
"Server VDR-a-VDR", // Italiano
"", // Nederlands
"", // Português
"", // Français
"VDR-to-VDR Serveur", // Français
"", // Norsk
"VDR-palvelin", // suomi
"", // Polski
@@ -348,7 +323,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"VDR-to-VDR áÕàÒÕà", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -363,7 +338,7 @@ const tI18nPhrase Phrases[] = {
"Server HTTP", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Serveur HTTP", // Français
"", // Norsk
"HTTP-palvelin", // suomi
"", // Polski
@@ -373,7 +348,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"HTTP áÕàÒÕà", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -382,15 +357,15 @@ const tI18nPhrase Phrases[] = {
"", // Türkçe
#endif
},
{ "VDR-to-VDR Client", // English
"VDR-zu-VDR Client", // Deutsch
{ "Minimum Priority", // English
"Minimale Priorität", // Deutsch
"", // Slovenski
"Client VDR-to-VDR", // Italiano
"Priorità minima", // Italiano
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"VDR-asiakas", // suomi
"Pienin prioriteetti", // suomi
"", // Polski
"", // Español
"", // Ellinika
@@ -407,40 +382,15 @@ const tI18nPhrase Phrases[] = {
"", // Türkçe
#endif
},
{ "Please restart VDR to activate changes", // English
"Bitte starten Sie für die Änderungen VDR neu", // Deutsch
{ "Maximum Priority", // English
"Maximale Priorität", // Deutsch
"", // Slovenski
"Riavviare VDR per attivare i cambiamenti", // Italiano
"Priorità massima", // Italiano
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"Aktivoi muutokset käynnistämällä VDR uudelleen", // suomi
"", // Polski
"", // Español
"", // Ellinika
"", // Svenska
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
"", // Czech
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Synchronize EPG", // English
"EPG synchronisieren", // Deutsch
"", // Slovenski
"", // Italiano
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"Päivitä ohjelmaopas", // suomi
"Suurin prioriteetti", // suomi
"", // Polski
"", // Español
"", // Ellinika
@@ -460,10 +410,10 @@ const tI18nPhrase Phrases[] = {
{ "Suspend Live TV", // English
"Live-TV pausieren", // Deutsch
"", // Slovenski
"", // Italiano
"Sospendi TV dal vivo", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Suspendre Live TV", // Français
"", // Norsk
"Pysäytä suora TV-lähetys", // suomi
"", // Polski
@@ -473,7 +423,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"¾áâÐÝÞÒÚÐ Live TV", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -485,10 +435,10 @@ const tI18nPhrase Phrases[] = {
{ "Suspend behaviour", // English
"Pausierverhalten", // Deutsch
"", // Slovenski
"", // Italiano
"Tipo sospensione", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Suspendre", // Français
"", // Norsk
"Pysäytystoiminto", // suomi
"", // Polski
@@ -498,7 +448,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"¿ÞÒÕÔÕÝØÕ ÞáâÐÝÞÒÚØ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -510,10 +460,10 @@ const tI18nPhrase Phrases[] = {
{ "Offer suspend mode", // English
"Pausieren anbieten", // Deutsch
"", // Slovenski
"", // Italiano
"Offri mod. sospensione", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Offrir le mode suspendre", // Français
"", // Norsk
"tyrkytä", // suomi
"", // Polski
@@ -523,7 +473,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"¿àÕÔÛÐÓÐâì ÞáâÐÝÞÒÚã", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -535,10 +485,10 @@ const tI18nPhrase Phrases[] = {
{ "Always suspended", // English
"Immer pausiert", // Deutsch
"", // Slovenski
"", // Italiano
"Sempre sospeso", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Toujours suspendre", // Français
"", // Norsk
"aina", // suomi
"", // Polski
@@ -548,7 +498,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"²áÕÓÔÐ ÞáâÐÝÞÒÛÕÝ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -560,10 +510,10 @@ const tI18nPhrase Phrases[] = {
{ "Never suspended", // English
"Nie pausiert", // Deutsch
"", // Slovenski
"", // Italiano
"Mai sospeso", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Jamais suspendre", // Français
"", // Norsk
"ei koskaan", // suomi
"", // Polski
@@ -573,7 +523,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"½ØÚÞÓÔÐ ÝÕ ÞáâÐÝÞÒÛÕÝ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -585,10 +535,10 @@ const tI18nPhrase Phrases[] = {
{ "Suspend Server", // English
"Server pausieren", // Deutsch
"", // Slovenski
"", // Italiano
"Sospendi Server", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Suspendre le serveur", // Français
"", // Norsk
"Pysäytä palvelin", // suomi
"", // Polski
@@ -598,7 +548,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"¾áâÐÝÞÒØâì áÕàÒÕà", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -610,10 +560,10 @@ const tI18nPhrase Phrases[] = {
{ "Server is suspended", // English
"Server ist pausiert", // Deutsch
"", // Slovenski
"", // Italiano
"Server sospeso", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Le serveur est suspendu", // Français
"", // Norsk
"Palvelin on pysäytetty", // suomi
"", // Polski
@@ -623,7 +573,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"ÁÕàÒÕà ÞáâÐÝÞÒÛÕÝ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -635,10 +585,10 @@ const tI18nPhrase Phrases[] = {
{ "Couldn't suspend Server!", // English
"Konnte Server nicht pausieren!", // Deutsch
"", // Slovenski
"", // Italiano
"Impossibile sospendere il server!", // Italiano
"", // Nederlands
"", // Português
"", // Français
"Impossible de suspendre le serveur!", // Français
"", // Norsk
"Palvelinta ei onnistuttu pysäyttämään!", // suomi
"", // Polski
@@ -648,7 +598,7 @@ const tI18nPhrase Phrases[] = {
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"ÝÕ ÜÞÓã ÞáâÐÝÞÒØâì áÕàÒÕà", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
@@ -660,12 +610,137 @@ const tI18nPhrase Phrases[] = {
{ "Client may suspend", // English
"Client darf pausieren", // Deutsch
"", // Slovenski
"", // Italiano
"Permetti sospensione al Client", // Italiano
"", // Nederlands
"", // Português
"Le client peut suspendre", // Français
"", // Norsk
"Asiakas saa pysäyttää palvelimen", // suomi
"", // Polski
"", // Español
"", // Ellinika
"", // Svenska
"", // Romaneste
"", // Magyar
"", // Catala
"ºÛØÕÝâ ÜÞÖÕâ ÞáâÐÝÐÒÛØÒÐâì", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
"", // Czech
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Bind to IP", // English
"Binde an IP", // Deutsch
"", // Slovenski
"IP associati", // Italiano
"", // Nederlands
"", // Português
"Attacher aux IP", // Français
"", // Norsk
"Sido osoitteeseen", // suomi
"", // Polski
"", // Español
"", // Ellinika
"", // Svenska
"", // Romaneste
"", // Magyar
"", // Catala
"¿àØáÞÕÔØÝØâìáï Ú IP", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
"", // Czech
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Filter Streaming", // English
"Filter-Daten streamen", // Deutsch
"", // Slovenski
"Filtra trasmissione", // Italiano
"", // Nederlands
"", // Português
"Filtre streaming", // Français
"", // Norsk
"Suodatetun tiedon suoratoisto", // suomi
"", // Polski
"", // Español
"", // Ellinika
"", // Svenska
"", // Romaneste
"", // Magyar
"", // Catala
"ÄØÛìâà ßÞâÞÚÐ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
"", // Czech
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Streaming active", // English
"Streamen im Gange", // Deutsch
"", // Slovenski
"Trasmissione attiva", // Italiano
"", // Nederlands
"", // Português
"Streaming actif", // Français
"", // Norsk
"Suoratoistopalvelin aktiivinen", // suomi
"", // Polski
"", // Español
"", // Ellinika
"", // Svenska
"", // Romaneste
"", // Magyar
"", // Catala
"ÁâàØÜØÝÓ ÐÚâØÒÕÝ", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
"", // Czech
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Hide Mainmenu Entry", // English
"Hauptmenüeintrag verstecken", // Deutsch
"", // Slovenski
"Nascondi voce menu principale", // Italiano
"", // Nederlands
"", // Português
"Masquer dans le menu principal", // Français
"", // Norsk
"Piilota valinta päävalikosta", // suomi
"", // Polski
"", // Español
"", // Ellinika
"", // Svenska
"", // Romaneste
"", // Magyar
"", // Catala
"ÁßàïâÐâì Ò ÓÛÐÒÝÞÜ ÜÕÝî", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
"", // Czech
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Multicast Streaming Server", // English
"Multicast Streaming Server", // Deutsch
"", // Slovenski
"Server trasmissione Multicast", // Italiano
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"Asiakas saa pysäyttää palvelimen", // suomi
"Multicast-suoratoistopalvelin", // suomi
"", // Polski
"", // Español
"", // Ellinika
@@ -682,15 +757,15 @@ const tI18nPhrase Phrases[] = {
"", // Türkçe
#endif
},
{ "Bind to IP", // English
"", // Deutsch
{ "Start IGMP Server", // English
"IGMP Server starten", // Deutsch
"", // Slovenski
"", // Italiano
"Avvia Server IGMP", // Italiano
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"Sido osoitteeseen", // suomi
"Käynnistä IGMP-palvelin", // suomi
"", // Polski
"", // Español
"", // Ellinika
@@ -706,16 +781,16 @@ const tI18nPhrase Phrases[] = {
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Filter Streaming", // English
"", // Deutsch
},
{ "Multicast Client Port", // English
"Port des Multicast Clients", // Deutsch
"", // Slovenski
"", // Italiano
"Porta Client Multicast", // Italiano
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"Suodatetun tiedon suoratoisto", // suomi
"Multicast-portti", // suomi
"", // Polski
"", // Español
"", // Ellinika
@@ -731,41 +806,16 @@ const tI18nPhrase Phrases[] = {
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Streaming active", // English
"Streamen im Gange", // Deutsch
},
{ "Multicast Streamtype", // English
"Multicast Streamtyp", // Deutsch
"", // Slovenski
"", // Italiano
"Tipo flusso Multicast", // Italiano
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"Suoratoistopalvelin aktiivinen", // suomi
"", // Polski
"", // Español
"", // Ellinika
"", // Svenska
"", // Romaneste
"", // Magyar
"", // Catala
"", // Russian
"", // Hrvatski
"", // Eesti
"", // Dansk
"", // Czech
#if VDRVERSNUM >= 10502
"", // Türkçe
#endif
},
{ "Hide Mainmenu Entry", // English
"Hauptmenüeintrag verstecken", // Deutsch
"", // Slovenski
"", // Italiano
"", // Nederlands
"", // Português
"", // Français
"", // Norsk
"Piilota valinta päävalikosta", // suomi
"Multicast-lähetysmuoto", // suomi
"", // Polski
"", // Español
"", // Ellinika

2
i18n.h
View File

@@ -1,5 +1,5 @@
/*
* $Id: i18n.h,v 1.1 2004/12/30 22:43:58 lordjaxom Exp $
* $Id: i18n.h,v 1.1.1.1 2004/12/30 22:43:58 lordjaxom Exp $
*/
#ifndef VDR_STREAMDEV_I18N_H

View File

@@ -106,7 +106,7 @@
#define MAX_PLENGTH 0xFFFF
#define MMAX_PLENGTH (8*MAX_PLENGTH)
#define MMAX_PLENGTH (64*MAX_PLENGTH)
#ifdef __cplusplus
extern "C" {

View File

@@ -1,43 +0,0 @@
# The cannels.conf ca field can be used to bind a channel to a specific
# device. The streamdev-client does not consider this information, so
# there's no way to keep VDR from using streamdev for a specific
# channel. Apply this patch if you need this feature.
#
# This fix should probably become part of streamdev. However as it
# changes the behaviour of streamdev, I decided to keep it as a separate
# patch until there is something like a new official streamdev release.
#
--- client/device.h.bak 2006-11-09 12:25:21.000000000 +0100
+++ client/device.h 2006-11-09 12:26:57.000000000 +0100
@@ -50,6 +50,7 @@
cStreamdevDevice(void);
virtual ~cStreamdevDevice();
+ virtual int ProvidesCa(const cChannel *Channel) const;
virtual bool ProvidesSource(int Source) const;
virtual bool ProvidesTransponder(const cChannel *Channel) const;
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1,
--- client/device.c.bak 2006-11-09 12:23:24.000000000 +0100
+++ client/device.c 2006-11-09 12:35:48.000000000 +0100
@@ -57,6 +57,12 @@
#endif
}
+int cStreamdevDevice::ProvidesCa(const cChannel *Channel) const
+{
+ // Encrypted is acceptable for now. Will ask the server later.
+ return Channel->Ca() <= CA_DVB_MAX ? cDevice::ProvidesCa(Channel) : 1;
+}
+
bool cStreamdevDevice::ProvidesSource(int Source) const {
Dprintf("ProvidesSource, Source=%d\n", Source);
return false;
@@ -78,7 +84,7 @@
if (ClientSocket.DataSocket(siLive) != NULL
&& TRANSPONDER(Channel, m_Channel))
res = true;
- else {
+ else if (ProvidesCa(Channel)) {
res = prio && ClientSocket.ProvidesChannel(Channel, Priority);
ndr = true;
}

View File

@@ -0,0 +1,11 @@
--- vdr.c.orig 2009-02-13 09:45:55.000000000 +0100
+++ vdr.c 2009-02-13 09:46:24.000000000 +0100
@@ -115,7 +115,7 @@
static bool SetCapSysTime(void)
{
// drop all capabilities except cap_sys_time
- cap_t caps = cap_from_text("= cap_sys_time=ep");
+ cap_t caps = cap_from_text("= cap_sys_time,cap_net_raw=ep");
if (!caps) {
fprintf(stderr, "vdr: cap_from_text failed: %s\n", strerror(errno));
return false;

View File

@@ -1,13 +1,18 @@
#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>
const char *g_ExternRemux = EXTERNREMUXPATH;
namespace Streamdev {
#define MAXENV 63
class cTSExt: public cThread {
private:
@@ -20,16 +25,19 @@ 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);
};
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter):
} // namespace Streamdev
using namespace Streamdev;
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids):
m_ResultBuffer(ResultBuffer),
m_Active(false),
m_Process(0),
m_Process(-1),
m_Inpipe(0),
m_Outpipe(0)
{
@@ -59,6 +67,118 @@ 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;
const static std::string PATH_INFO("PATH_INFO");
tStrStrMap::const_iterator it_pathinfo = Connection->Headers().find(PATH_INFO);
const std::string& path = it_pathinfo == Connection->Headers().end() ? "/" : it_pathinfo->second;
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);
@@ -68,9 +188,20 @@ 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(g_ExternRemux) + " " + Parameter;
execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL);
_exit(-1);
if (setpgid(0, 0) == -1)
esyslog("streamdev-server: externremux setpgid failed: %m");
if (access(opt_remux, X_OK) == -1) {
esyslog("streamdev-server %s: %m", opt_remux);
_exit(-1);
}
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
_exit(0);
}
close(inpipe[0]);
@@ -85,16 +216,31 @@ cTSExt::~cTSExt()
m_Active = false;
Cancel(3);
if (m_Process > 0) {
// close pipes
close(m_Outpipe);
close(m_Inpipe);
kill(m_Process, SIGTERM);
for (int i = 0; waitpid(m_Process, NULL, WNOHANG) == 0; i++) {
if (i == 20) {
esyslog("streamdev-server: externremux process won't stop - killing it");
kill(m_Process, SIGKILL);
}
cCondWait::SleepMs(100);
// signal and wait for termination
if (kill(m_Process, SIGINT) < 0) {
esyslog("streamdev-server: externremux SIGINT failed: %m");
}
else {
int i = 0;
int retval;
while ((retval = waitpid(m_Process, NULL, WNOHANG)) == 0) {
if ((++i % 20) == 0) {
esyslog("streamdev-server: externremux process won't stop - killing it");
kill(m_Process, SIGKILL);
}
cCondWait::SleepMs(100);
}
if (retval < 0)
esyslog("streamdev-server: externremux process waitpid failed: %m");
else
Dprintf("streamdev-server: externremux child (%d) exited as expected\n", m_Process);
}
m_Process = -1;
}
}
@@ -150,9 +296,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);
}

View File

@@ -5,7 +5,10 @@
#include <vdr/ringbuffer.h>
#include <string>
extern const char *g_ExternRemux;
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);
@@ -23,4 +26,6 @@ public:
void Del(int Count) { m_ResultBuffer->Del(Count); }
};
} // namespace Streamdev
#endif // VDR_STREAMDEV_EXTERNREMUX_H

View File

@@ -1,12 +1,13 @@
#include "remux/ts2es.h"
#include "server/streamer.h"
#include "libdvbmpeg/transform.h"
#include "common.h"
#include <vdr/device.h>
// from VDR's remux.c
#define MAXNONUSEFULDATA (10*1024*1024)
namespace Streamdev {
class cTS2ES: public ipack {
friend void PutES(uint8_t *Buffer, int Size, void *Data);
@@ -32,6 +33,9 @@ void PutES(uint8_t *Buffer, int Size, void *Data)
This->start = 1;
}
} // namespace Streamdev
using namespace Streamdev;
cTS2ES::cTS2ES(cRingBufferLinear *ResultBuffer)
{
m_ResultBuffer = ResultBuffer;
@@ -75,10 +79,10 @@ void cTS2ES::PutTSPacket(const uint8_t *Buffer) {
cTS2ESRemux::cTS2ESRemux(int Pid):
m_Pid(Pid),
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, IPACKS)),
m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)),
m_Remux(new cTS2ES(m_ResultBuffer))
{
m_ResultBuffer->SetTimeouts(0, 100);
m_ResultBuffer->SetTimeouts(100, 100);
}
cTS2ESRemux::~cTS2ESRemux()
@@ -111,8 +115,10 @@ int cTS2ESRemux::Put(const uchar *Data, int Count)
break;
if (Data[i] != TS_SYNC_BYTE)
break;
if (m_ResultBuffer->Free() < 2 * IPACKS)
if (m_ResultBuffer->Free() < 2 * IPACKS) {
m_ResultBuffer->WaitForPut();
break; // A cTS2ES might write one full packet and also a small rest
}
int pid = cTSRemux::GetPid(Data + i + 1);
if (Data[i + 3] & 0x10) { // got payload
if (m_Pid == pid)

View File

@@ -2,15 +2,16 @@
#define VDR_STREAMDEV_TS2ESREMUX_H
#include "remux/tsremux.h"
#include <vdr/ringbuffer.h>
#include "server/streamer.h"
namespace Streamdev {
class cTS2ES;
class cRingBufferLinear;
class cTS2ESRemux: public cTSRemux {
private:
int m_Pid;
cRingBufferLinear *m_ResultBuffer;
cStreamdevBuffer *m_ResultBuffer;
cTS2ES *m_Remux;
public:
@@ -22,4 +23,6 @@ public:
void Del(int Count) { m_ResultBuffer->Del(Count); }
};
} // namespace Streamdev
#endif // VDR_STREAMDEV_TS2ESREMUX_H

1993
remux/ts2pes.c Normal file

File diff suppressed because it is too large Load Diff

56
remux/ts2pes.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* ts2pes.h: A streaming MPEG2 remultiplexer
*
* This file is based on a copy of remux.h from Klaus Schmidinger's
* VDR, version 1.6.0.
*
* $Id: ts2pes.h,v 1.2.2.4 2009/07/06 06:13:41 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_TS2PES_H
#define VDR_STREAMDEV_TS2PES_H
#include "remux/tsremux.h"
#include "server/streamer.h"
#define MAXTRACKS 64
namespace Streamdev {
class cTS2PES;
class cTS2PESRemux: public cTSRemux {
private:
bool noVideo;
bool synced;
int skipped;
cTS2PES *ts2pes[MAXTRACKS];
int numTracks;
cStreamdevBuffer *resultBuffer;
int resultSkipped;
public:
cTS2PESRemux(int VPid, const int *APids, const int *DPids, const int *SPids);
///< Creates a new remuxer for the given PIDs. VPid is the video PID, while
///< APids, DPids and SPids are pointers to zero terminated lists of audio,
///< dolby and subtitle PIDs (the pointers may be NULL if there is no such
///< PID).
virtual ~cTS2PESRemux();
int Put(const uchar *Data, int Count);
///< Puts at most Count bytes of Data into the remuxer.
///< \return Returns the number of bytes actually consumed from Data.
uchar *Get(int &Count);
///< Gets all currently available data from the remuxer.
///< \return Count contains the number of bytes the result points to, and
void Del(int Count);
///< Deletes Count bytes from the remuxer. Count must be the number returned
///< from a previous call to Get(). Several calls to Del() with fractions of
///< a previously returned Count may be made, but the total sum of all Count
///< values must be exactly what the previous Get() has returned.
void Clear(void);
///< Clears the remuxer of all data it might still contain, keeping the PID
///< settings as they are.
};
} // namespace Streamdev
#endif // VDR_STREAMDEV_TS2PES_H

View File

@@ -3,6 +3,8 @@
#include <vdr/channels.h>
#include <vdr/device.h>
namespace Streamdev {
class cTS2PS {
friend void PutPES(uint8_t *Buffer, int Size, void *Data);
@@ -28,6 +30,9 @@ void PutPES(uint8_t *Buffer, int Size, void *Data)
esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Size - n, Size);
}
} // namespace Streamdev
using namespace Streamdev;
cTS2PS::cTS2PS(cRingBufferLinear *ResultBuffer, int Pid, uint8_t AudioCid)
{
m_ResultBuffer = ResultBuffer;
@@ -74,13 +79,13 @@ void cTS2PS::PutTSPacket(const uint8_t *Buffer)
cTS2PSRemux::cTS2PSRemux(int VPid, const int *APids, const int *DPids, const int *SPids):
m_NumTracks(0),
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, IPACKS)),
m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)),
m_ResultSkipped(0),
m_Skipped(0),
m_Synced(false),
m_IsRadio(VPid == 0 || VPid == 1 || VPid == 0x1FFF)
{
m_ResultBuffer->SetTimeouts(0, 100);
m_ResultBuffer->SetTimeouts(100, 100);
if (VPid)
m_Remux[m_NumTracks++] = new cTS2PS(m_ResultBuffer, VPid);
@@ -124,8 +129,10 @@ int cTS2PSRemux::Put(const uchar *Data, int Count)
break;
if (Data[i] != TS_SYNC_BYTE)
break;
if (m_ResultBuffer->Free() < 2 * IPACKS)
if (m_ResultBuffer->Free() < 2 * IPACKS) {
m_ResultBuffer->WaitForPut();
break; // A cTS2PS might write one full packet and also a small rest
}
int pid = GetPid(Data + i + 1);
if (Data[i + 3] & 0x10) { // got payload
for (int t = 0; t < m_NumTracks; t++) {

View File

@@ -1,9 +1,10 @@
#ifndef VDR_STREAMDEV_TS2PESREMUX_H
#define VDR_STREAMDEV_TS2PESREMUX_H
#ifndef VDR_STREAMDEV_TS2PSREMUX_H
#define VDR_STREAMDEV_TS2PSREMUX_H
#include "remux/tsremux.h"
#include <vdr/remux.h>
#include <vdr/ringbuffer.h>
#include "server/streamer.h"
namespace Streamdev {
class cTS2PS;
@@ -11,7 +12,7 @@ class cTS2PSRemux: public cTSRemux {
private:
int m_NumTracks;
cTS2PS *m_Remux[MAXTRACKS];
cRingBufferLinear *m_ResultBuffer;
cStreamdevBuffer *m_ResultBuffer;
int m_ResultSkipped;
int m_Skipped;
bool m_Synced;
@@ -26,4 +27,6 @@ public:
void Del(int Count) { m_ResultBuffer->Del(Count); }
};
#endif // VDR_STREAMDEV_TS2PESREMUX_H
} // namespace Streamdev
#endif // VDR_STREAMDEV_TS2PSREMUX_H

View File

@@ -2,10 +2,15 @@
#define SC_PICTURE 0x00 // "picture header"
#define VIDEO_STREAM_S 0xE0
using namespace Streamdev;
void cTSRemux::SetBrokenLink(uchar *Data, int Length)
{
if (Length > 9 && Data[0] == 0 && Data[1] == 0 && Data[2] == 1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) {
for (int i = Data[8] + 9; i < Length - 7; i++) { // +9 to skip video packet header
int PesPayloadOffset = 0;
if (AnalyzePesHeader(Data, Length, PesPayloadOffset) >= phMPEG1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) {
for (int i = PesPayloadOffset; i < Length - 7; i++) {
if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1 && Data[i + 3] == 0xB8) {
if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed
Data[i + 7] |= 0x20;
@@ -39,17 +44,40 @@ int cTSRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &P
// If the return value is -1 the packet was not completely in the buffer.
int Length = GetPacketLength(Data, Count, Offset);
if (Length > 0) {
if (Length >= 8) {
int i = Offset + 8; // the minimum length of the video packet header
i += Data[i] + 1; // possible additional header bytes
for (; i < Offset + Length - 5; i++) {
if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) {
switch (Data[i + 3]) {
case SC_PICTURE: PictureType = (Data[i + 5] >> 3) & 0x07;
return Length;
int PesPayloadOffset = 0;
if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) {
const uchar *p = Data + Offset + PesPayloadOffset + 2;
const uchar *pLimit = Data + Offset + Length - 3;
#ifdef TEST_cVideoRepacker
// cVideoRepacker ensures that a new PES packet is started for a new sequence,
// group or picture which allows us to easily skip scanning through a huge
// amount of video data.
if (p < pLimit) {
if (p[-2] || p[-1] || p[0] != 0x01)
pLimit = 0; // skip scanning: packet doesn't start with 0x000001
else {
switch (p[1]) {
case SC_SEQUENCE:
case SC_GROUP:
case SC_PICTURE:
break;
default: // skip scanning: packet doesn't start a new sequence, group or picture
pLimit = 0;
}
}
}
#endif
while (p < pLimit && (p = (const uchar *)memchr(p, 0x01, pLimit - p))) {
if (!p[-2] && !p[-1]) { // found 0x000001
switch (p[1]) {
case SC_PICTURE: PictureType = (p[3] >> 3) & 0x07;
return Length;
}
p += 4; // continue scanning after 0x01ssxxyy
}
}
}
else
p += 3; // continue scanning after 0x01xxyy
}
}
PictureType = NO_PICTURE;
return Length;

View File

@@ -4,30 +4,25 @@
#include "libdvbmpeg/transform.h"
#include <vdr/remux.h>
#define RESULTBUFFERSIZE KILOBYTE(256)
// Picture types:
#define NO_PICTURE 0
namespace Streamdev {
class cTSRemux {
protected:
/*uchar m_ResultBuffer[RESULTBUFFERSIZE];
int m_ResultCount;
int m_ResultDelivered;
int m_Synced;
int m_Skipped;
int m_Sync;
virtual void PutTSPacket(int Pid, const uint8_t *Data) = 0;
public:
cTSRemux(bool Sync = true);
virtual ~cTSRemux();
virtual ~cTSRemux() {};
virtual uchar *Process(const uchar *Data, int &Count, int &Result);*/
virtual int Put(const uchar *Data, int Count) = 0;
virtual uchar *Get(int &Count) = 0;
virtual void Del(int Count) = 0;
static void SetBrokenLink(uchar *Data, int Length);
static int GetPid(const uchar *Data);
static int GetPacketLength(const uchar *Data, int Count, int Offset);
static int GetPacketLength(const uchar *Data, int Count, int Offset);
static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType);
};
} // namespace Streamdev
#endif // VDR_STREAMDEV_TSREMUX_H

View File

@@ -1,13 +1,14 @@
/*
* $Id: component.c,v 1.3 2005/05/09 20:22:29 lordjaxom Exp $
* $Id: component.c,v 1.3.2.1 2009/02/13 10:39:42 schmirl Exp $
*/
#include "server/component.h"
#include "server/connection.h"
cServerComponent::cServerComponent(const char *Protocol, const char *ListenIp,
uint ListenPort):
uint ListenPort, int Type, int IpProto):
m_Protocol(Protocol),
m_Listen(Type, IpProto),
m_ListenIp(ListenIp),
m_ListenPort(ListenPort)
{

View File

@@ -1,5 +1,5 @@
/*
* $Id: component.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
* $Id: component.h,v 1.2.2.1 2009/02/13 10:39:42 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_SERVERS_COMPONENT_H
@@ -17,8 +17,8 @@ class cServerConnection;
class cServerComponent: public cListObject {
private:
cTBSocket m_Listen;
const char *m_Protocol;
cTBSocket m_Listen;
const char *m_ListenIp;
uint m_ListenPort;
@@ -27,7 +27,7 @@ protected:
virtual cServerConnection *NewClient(void) = 0;
public:
cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort);
cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort, int Type = SOCK_STREAM, int IpProto = 0);
virtual ~cServerComponent();
/* Starts listening on the specified Port, override if you want to do things

447
server/componentIGMP.c Normal file
View File

@@ -0,0 +1,447 @@
/*
* $Id: componentIGMP.c,v 1.1.2.3 2009/07/03 21:42:08 schmirl Exp $
*/
#include <netinet/ip.h>
#include <netinet/igmp.h>
#include "server/componentIGMP.h"
#include "server/connectionIGMP.h"
#include "server/setup.h"
#ifndef IGMP_ALL_HOSTS
#define IGMP_ALL_HOSTS htonl(0xE0000001L)
#endif
#ifndef IGMP_ALL_ROUTER
#define IGMP_ALL_ROUTER htonl(0xE0000002L)
#endif
// IGMP parameters according to RFC2236. All time values in seconds.
#define IGMP_ROBUSTNESS 2
#define IGMP_QUERY_INTERVAL 125
#define IGMP_QUERY_RESPONSE_INTERVAL 10
#define IGMP_GROUP_MEMBERSHIP_INTERVAL (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL)
#define IGMP_OTHER_QUERIER_PRESENT_INTERVAL (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL / 2)
#define IGMP_STARTUP_QUERY_INTERVAL (IGMP_QUERY_INTERVAL / 4)
#define IGMP_STARTUP_QUERY_COUNT IGMP_ROBUSTNESS
// This value is 1/10 sec. RFC default is 10. Reduced to minimum to free unused channels ASAP
#define IGMP_LAST_MEMBER_QUERY_INTERVAL_TS 1
#define IGMP_LAST_MEMBER_QUERY_COUNT IGMP_ROBUSTNESS
// operations on struct timeval
#define TV_CMP(a, cmp, b) (a.tv_sec == b.tv_sec ? a.tv_usec cmp b.tv_usec : a.tv_sec cmp b.tv_sec)
#define TV_SET(tv) (tv.tv_sec || tv.tv_usec)
#define TV_CLR(tv) memset(&tv, 0, sizeof(tv))
#define TV_CPY(dst, src) memcpy(&dst, &src, sizeof(dst))
#define TV_ADD(dst, ts) dst.tv_sec += ts / 10; dst.tv_usec += (ts % 10) * 100000; if (dst.tv_usec >= 1000000) { dst.tv_usec -= 1000000; dst.tv_sec++; }
class cMulticastGroup: public cListObject
{
public:
cConnectionIGMP *connection;
in_addr_t group;
in_addr_t reporter;
struct timeval timeout;
struct timeval v1timer;
struct timeval retransmit;
cMulticastGroup(in_addr_t Group);
};
cMulticastGroup::cMulticastGroup(in_addr_t Group) :
connection(NULL),
group(Group),
reporter(0)
{
TV_CLR(timeout);
TV_CLR(v1timer);
TV_CLR(retransmit);
}
void logIGMP(uint8_t type, struct in_addr Src, struct in_addr Dst, struct in_addr Grp)
{
const char* msg;
switch (type) {
case IGMP_MEMBERSHIP_QUERY: msg = "membership query"; break;
case IGMP_V1_MEMBERSHIP_REPORT: msg = "V1 membership report"; break;
case IGMP_V2_MEMBERSHIP_REPORT: msg = "V2 membership report"; break;
case IGMP_V2_LEAVE_GROUP: msg = "leave group"; break;
default: msg = "unknown"; break;
}
char* s = strdup(inet_ntoa(Src));
char* d = strdup(inet_ntoa(Dst));
dsyslog("streamdev-server IGMP: Received %s from %s (dst %s) for %s", msg, s, d, inet_ntoa(Grp));
free(s);
free(d);
}
/* Taken from http://tools.ietf.org/html/rfc1071 */
uint16_t inetChecksum(uint16_t *addr, int count)
{
uint32_t sum = 0;
while (count > 1) {
sum += *addr++;
count -= 2;
}
if( count > 0 )
sum += * (uint8_t *) addr;
while (sum>>16)
sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
cComponentIGMP::cComponentIGMP(void):
cServerComponent("IGMP", "0.0.0.0", 0, SOCK_RAW, IPPROTO_IGMP),
cThread("IGMP timeout handler"),
m_BindIp(inet_addr(StreamdevServerSetup.IGMPBindIP)),
m_MaxChannelNumber(0),
m_StartupQueryCount(IGMP_STARTUP_QUERY_COUNT),
m_Querier(true)
{
}
cComponentIGMP::~cComponentIGMP(void)
{
}
cMulticastGroup* cComponentIGMP::FindGroup(in_addr_t Group) const
{
cMulticastGroup *group = m_Groups.First();
while (group && group->group != Group)
group = m_Groups.Next(group);
return group;
}
bool cComponentIGMP::Initialize(void)
{
if (cServerComponent::Initialize() && IGMPMembership(IGMP_ALL_ROUTER))
{
for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
{
if (channel->GroupSep())
continue;
int num = channel->Number();
if (!IGMPMembership(htonl(MULTICAST_PRIV_MIN + num)))
break;
m_MaxChannelNumber = num;
}
if (m_MaxChannelNumber == 0)
{
IGMPMembership(IGMP_ALL_ROUTER, false);
esyslog("streamdev-server IGMP: no multicast group joined");
}
else
{
Start();
}
}
return m_MaxChannelNumber > 0;
}
void cComponentIGMP::Destruct(void)
{
if (m_MaxChannelNumber > 0)
{
Cancel(3);
for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
{
if (channel->GroupSep())
continue;
int num = channel->Number();
if (num > m_MaxChannelNumber)
break;
IGMPMembership(htonl(MULTICAST_PRIV_MIN + num), false);
}
IGMPMembership(IGMP_ALL_ROUTER, false);
}
m_MaxChannelNumber = 0;
cServerComponent::Destruct();
}
cServerConnection *cComponentIGMP::NewClient(void)
{
return new cConnectionIGMP("IGMP", StreamdevServerSetup.IGMPClientPort, (eStreamType) StreamdevServerSetup.IGMPStreamType);
}
cServerConnection* cComponentIGMP::Accept(void)
{
ssize_t recv_len;
int ip_hdrlen, ip_datalen;
struct ip *ip;
struct igmp *igmp;
while ((recv_len = ::recvfrom(Socket(), m_ReadBuffer, sizeof(m_ReadBuffer), 0, NULL, NULL)) < 0 && errno == EINTR)
errno = 0;
if (recv_len < 0) {
esyslog("streamdev-server IGMP: read failed: %m");
return NULL;
}
else if (recv_len < (ssize_t) sizeof(struct ip)) {
esyslog("streamdev-server IGMP: IP packet too short");
return NULL;
}
ip = (struct ip*) m_ReadBuffer;
// filter out my own packets
if (ip->ip_src.s_addr == m_BindIp)
return NULL;
ip_hdrlen = ip->ip_hl << 2;
#ifdef __FreeBSD__
ip_datalen = ip->ip_len;
#else
ip_datalen = ntohs(ip->ip_len) - ip_hdrlen;
#endif
if (ip->ip_p != IPPROTO_IGMP) {
esyslog("streamdev-server IGMP: Unexpected protocol %hhu", ip->ip_p);
return NULL;
}
if (recv_len < ip_hdrlen + IGMP_MINLEN) {
esyslog("streamdev-server IGMP: packet too short");
return NULL;
}
igmp = (struct igmp*) (m_ReadBuffer + ip_hdrlen);
uint16_t chksum = igmp->igmp_cksum;
igmp->igmp_cksum = 0;
if (chksum != inetChecksum((uint16_t *)igmp, ip_datalen))
{
esyslog("INVALID CHECKSUM %d %d %d %lu 0x%x 0x%x", (int) ntohs(ip->ip_len), ip_hdrlen, ip_datalen, (unsigned long int) recv_len, chksum, inetChecksum((uint16_t *)igmp, ip_datalen));
return NULL;
}
logIGMP(igmp->igmp_type, ip->ip_src, ip->ip_dst, igmp->igmp_group);
return ProcessMessage(igmp, igmp->igmp_group.s_addr, ip->ip_src.s_addr);
}
cServerConnection* cComponentIGMP::ProcessMessage(struct igmp *Igmp, in_addr_t Group, in_addr_t Sender)
{
cServerConnection* conn = NULL;
cMulticastGroup* group;
LOCK_THREAD;
switch (Igmp->igmp_type) {
case IGMP_MEMBERSHIP_QUERY:
if (ntohl(Sender) < ntohl(m_BindIp))
IGMPStartOtherQuerierPresentTimer();
break;
case IGMP_V1_MEMBERSHIP_REPORT:
case IGMP_V2_MEMBERSHIP_REPORT:
group = FindGroup(Group);
if (!group) {
group = new cMulticastGroup(Group);
m_Groups.Add(group);
}
if (!group->connection) {
IGMPStartMulticast(group);
conn = group->connection;
}
IGMPStartTimer(group, Sender);
if (Igmp->igmp_type == IGMP_V1_MEMBERSHIP_REPORT)
IGMPStartV1HostTimer(group);
break;
case IGMP_V2_LEAVE_GROUP:
group = FindGroup(Group);
if (group && !TV_SET(group->v1timer)) {
if (group->reporter == Sender) {
IGMPStartTimerAfterLeave(group, m_Querier ? IGMP_LAST_MEMBER_QUERY_INTERVAL_TS : Igmp->igmp_code);
if (m_Querier)
IGMPSendGroupQuery(group);
IGMPStartRetransmitTimer(group);
}
m_CondWait.Signal();
}
break;
default:
break;
}
return conn;
}
void cComponentIGMP::Action()
{
while (Running()) {
struct timeval now;
struct timeval next;
gettimeofday(&now, NULL);
TV_CPY(next, now);
next.tv_sec += IGMP_QUERY_INTERVAL;
cMulticastGroup *del = NULL;
{
LOCK_THREAD;
if (TV_CMP(m_GeneralQueryTimer, <, now)) {
dsyslog("General Query");
IGMPSendGeneralQuery();
IGMPStartGeneralQueryTimer();
}
if (TV_CMP(next, >, m_GeneralQueryTimer))
TV_CPY(next, m_GeneralQueryTimer);
for (cMulticastGroup *group = m_Groups.First(); group; group = m_Groups.Next(group)) {
if (TV_CMP(group->timeout, <, now)) {
IGMPStopMulticast(group);
IGMPClearRetransmitTimer(group);
if (del)
m_Groups.Del(del);
del = group;
}
else if (m_Querier && TV_SET(group->retransmit) && TV_CMP(group->retransmit, <, now)) {
IGMPSendGroupQuery(group);
IGMPStartRetransmitTimer(group);
if (TV_CMP(next, >, group->retransmit))
TV_CPY(next, group->retransmit);
}
else if (TV_SET(group->v1timer) && TV_CMP(group->v1timer, <, now)) {
TV_CLR(group->v1timer);
}
else {
if (TV_CMP(next, >, group->timeout))
TV_CPY(next, group->timeout);
if (TV_SET(group->retransmit) && TV_CMP(next, >, group->retransmit))
TV_CPY(next, group->retransmit);
if (TV_SET(group->v1timer) && TV_CMP(next, >, group->v1timer))
TV_CPY(next, group->v1timer);
}
}
if (del)
m_Groups.Del(del);
}
int sleep = (next.tv_sec - now.tv_sec) * 1000;
sleep += (next.tv_usec - now.tv_usec) / 1000;
if (next.tv_usec < now.tv_usec)
sleep += 1000;
dsyslog("Sleeping %d ms", sleep);
m_CondWait.Wait(sleep);
}
}
bool cComponentIGMP::IGMPMembership(in_addr_t Group, bool Add)
{
struct ip_mreqn mreq;
mreq.imr_multiaddr.s_addr = Group;
mreq.imr_address.s_addr = INADDR_ANY;
mreq.imr_ifindex = 0;
if (setsockopt(Socket(), IPPROTO_IP, Add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
{
esyslog("streamdev-server IGMP: unable to %s %s: %m", Add ? "join" : "leave", inet_ntoa(mreq.imr_multiaddr));
if (errno == ENOBUFS)
esyslog("consider increasing sys.net.ipv4.igmp_max_memberships");
return false;
}
return true;
}
void cComponentIGMP::IGMPSendQuery(in_addr_t Group, int Timeout)
{
struct sockaddr_in dst;
struct igmp query;
dst.sin_family = AF_INET;
dst.sin_port = IPPROTO_IGMP;
dst.sin_addr.s_addr = Group;
query.igmp_type = IGMP_MEMBERSHIP_QUERY;
query.igmp_code = Timeout * 10;
query.igmp_cksum = 0;
query.igmp_group.s_addr = (Group == IGMP_ALL_HOSTS) ? 0 : Group;
query.igmp_cksum = inetChecksum((uint16_t *) &query, sizeof(query));
for (int i = 0; i < 5 && ::sendto(Socket(), &query, sizeof(query), 0, (sockaddr*)&dst, sizeof(dst)) == -1; i++) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
esyslog("streamdev-server IGMP: unable to query group %s: %m", inet_ntoa(dst.sin_addr));
break;
}
cCondWait::SleepMs(10);
}
}
// Querier state actions
void cComponentIGMP::IGMPStartGeneralQueryTimer()
{
m_Querier = true;
if (m_StartupQueryCount) {
gettimeofday(&m_GeneralQueryTimer, NULL);
m_GeneralQueryTimer.tv_sec += IGMP_STARTUP_QUERY_INTERVAL;
m_StartupQueryCount--;
}
else {
gettimeofday(&m_GeneralQueryTimer, NULL);
m_GeneralQueryTimer.tv_sec += IGMP_QUERY_INTERVAL;
}
}
void cComponentIGMP::IGMPStartOtherQuerierPresentTimer()
{
m_Querier = false;
m_StartupQueryCount = 0;
gettimeofday(&m_GeneralQueryTimer, NULL);
m_GeneralQueryTimer.tv_sec += IGMP_OTHER_QUERIER_PRESENT_INTERVAL;
}
void cComponentIGMP::IGMPSendGeneralQuery()
{
IGMPSendQuery(IGMP_ALL_HOSTS, IGMP_QUERY_RESPONSE_INTERVAL);
}
// Group state actions
void cComponentIGMP::IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member)
{
gettimeofday(&Group->timeout, NULL);
Group->timeout.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL;
TV_CLR(Group->retransmit);
Group->reporter = Member;
}
void cComponentIGMP::IGMPStartV1HostTimer(cMulticastGroup* Group)
{
gettimeofday(&Group->v1timer, NULL);
Group->v1timer.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL;
}
void cComponentIGMP::IGMPStartTimerAfterLeave(cMulticastGroup* Group, unsigned int MaxResponseTimeTs)
{
//Group->Update(time(NULL) + MaxResponseTime * IGMP_LAST_MEMBER_QUERY_COUNT / 10);
MaxResponseTimeTs *= IGMP_LAST_MEMBER_QUERY_COUNT;
gettimeofday(&Group->timeout, NULL);
TV_ADD(Group->timeout, MaxResponseTimeTs);
TV_CLR(Group->retransmit);
Group->reporter = 0;
}
void cComponentIGMP::IGMPStartRetransmitTimer(cMulticastGroup* Group)
{
gettimeofday(&Group->retransmit, NULL);
TV_ADD(Group->retransmit, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
}
void cComponentIGMP::IGMPClearRetransmitTimer(cMulticastGroup* Group)
{
TV_CLR(Group->retransmit);
}
void cComponentIGMP::IGMPSendGroupQuery(cMulticastGroup* Group)
{
IGMPSendQuery(Group->group, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS);
}
void cComponentIGMP::IGMPStartMulticast(cMulticastGroup* Group)
{
in_addr_t g = ntohl(Group->group);
if (g > MULTICAST_PRIV_MIN && g <= MULTICAST_PRIV_MAX) {
cChannel *channel = Channels.GetByNumber(g - MULTICAST_PRIV_MIN);
Group->connection = (cConnectionIGMP*) NewClient();
if (!Group->connection->Start(channel, Group->group)) {
DELETENULL(Group->connection);
}
}
}
void cComponentIGMP::IGMPStopMulticast(cMulticastGroup* Group)
{
if (Group->connection)
Group->connection->Stop();
}

62
server/componentIGMP.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* $Id: componentIGMP.h,v 1.1.2.2 2009/02/13 10:39:42 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_IGMPSERVER_H
#define VDR_STREAMDEV_IGMPSERVER_H
#include <sys/time.h>
#include <time.h>
#include <vdr/thread.h>
#include "server/component.h"
class cConnectionIGMP;
class cMulticastGroup;
class cComponentIGMP: public cServerComponent, public cThread {
private:
char m_ReadBuffer[2048];
cList<cMulticastGroup> m_Groups;
in_addr_t m_BindIp;
int m_MaxChannelNumber;
struct timeval m_GeneralQueryTimer;
int m_StartupQueryCount;
bool m_Querier;
cCondWait m_CondWait;
cMulticastGroup* FindGroup(in_addr_t Group) const;
/* Add or remove local host to multicast group */
bool IGMPMembership(in_addr_t Group, bool Add = true);
void IGMPSendQuery(in_addr_t Group, int Timeout);
cServerConnection* ProcessMessage(struct igmp *Igmp, in_addr_t Group, in_addr_t Sender);
void IGMPStartGeneralQueryTimer();
void IGMPStartOtherQuerierPresentTimer();
void IGMPSendGeneralQuery();
void IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member);
void IGMPStartV1HostTimer(cMulticastGroup* Group);
void IGMPStartTimerAfterLeave(cMulticastGroup* Group, unsigned int MaxResponseTime);
void IGMPStartRetransmitTimer(cMulticastGroup* Group);
void IGMPClearRetransmitTimer(cMulticastGroup* Group);
void IGMPSendGroupQuery(cMulticastGroup* Group);
void IGMPStartMulticast(cMulticastGroup* Group);
void IGMPStopMulticast(cMulticastGroup* Group);
virtual void Action();
protected:
virtual cServerConnection *NewClient(void);
public:
virtual bool Initialize(void);
virtual void Destruct(void);
virtual cServerConnection* Accept(void);
cComponentIGMP(void);
~cComponentIGMP(void);
};
#endif // VDR_STREAMDEV_IGMPSERVER_H

View File

@@ -1,5 +1,5 @@
/*
* $Id: connection.c,v 1.10 2007/05/07 12:25:11 schmirl Exp $
* $Id: connection.c,v 1.10.2.4 2010/08/03 10:56:58 schmirl Exp $
*/
#include "server/connection.h"
@@ -12,7 +12,8 @@
#include <stdarg.h>
#include <errno.h>
cServerConnection::cServerConnection(const char *Protocol):
cServerConnection::cServerConnection(const char *Protocol, int Type):
cTBSocket(Type),
m_Protocol(Protocol),
m_DeferClose(false),
m_Pending(false),
@@ -26,6 +27,66 @@ cServerConnection::~cServerConnection()
{
}
const cChannel* cServerConnection::ChannelFromString(const char *String, int *Apid, int *Dpid) {
const cChannel *channel = NULL;
char *string = strdup(String);
char *ptr, *end;
int apididx = 0;
if ((ptr = strrchr(string, '+')) != NULL) {
*(ptr++) = '\0';
apididx = strtoul(ptr, &end, 10);
Dprintf("found apididx: %d\n", apididx);
}
if (isnumber(string)) {
int temp = strtol(String, NULL, 10);
if (temp >= 1 && temp <= Channels.MaxNumber())
channel = Channels.GetByNumber(temp);
} else {
channel = Channels.GetByChannelID(tChannelID::FromString(string));
if (channel == NULL) {
int i = 1;
while ((channel = Channels.GetByNumber(i, 1)) != NULL) {
if (String == channel->Name())
break;
i = channel->Number() + 1;
}
}
}
if (channel != NULL && apididx > 0) {
int apid = 0, dpid = 0;
int index = 1;
for (int i = 0; channel->Apid(i) != 0; ++i, ++index) {
if (index == apididx) {
apid = channel->Apid(i);
break;
}
}
if (apid == 0) {
for (int i = 0; channel->Dpid(i) != 0; ++i, ++index) {
if (index == apididx) {
dpid = channel->Dpid(i);
break;
}
}
}
if (Apid != NULL)
*Apid = apid;
if (Dpid != NULL)
*Dpid = dpid;
}
free(string);
return channel;
}
bool cServerConnection::Read(void)
{
int b;
@@ -193,12 +254,16 @@ cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority)
}
Dprintf(" * Found device for live tv: %p (%d)\n", newdev,
newdev ? newdev->CardIndex() + 1 : 0);
if (newdev == NULL || newdev == device)
if (newdev == NULL || newdev == device) {
// no suitable device to continue live TV, giving up...
device = NULL;
dsyslog("streamdev: Not providing channel %s at priority %d - live TV not suspended", Channel->Name(), Priority);
}
else
newdev->SwitchChannel(current, true);
}
else if (!device)
dsyslog("streamdev: No device provides channel %s at priority %d", Channel->Name(), Priority);
}
return device;

View File

@@ -1,5 +1,5 @@
/*
* $Id: connection.h,v 1.5 2007/04/16 11:01:02 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,12 +48,20 @@ protected:
virtual bool Respond(const char *Message, bool Last = true, ...);
//__attribute__ ((format (printf, 2, 4)));
/* 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
will be displayed in error messages */
cServerConnection(const char *Protocol);
cServerConnection(const char *Protocol, int Type = SOCK_STREAM);
virtual ~cServerConnection();
/* If true, any client IP will be accepted */
virtual bool CanAuthenticate(void) { return false; }
/* Gets called if the client has been accepted by the core */
virtual void Welcome(void) { }
@@ -84,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

View File

@@ -1,24 +1,25 @@
/*
* $Id: connectionHTTP.c,v 1.13 2008/03/28 15:11:40 schmirl Exp $
* $Id: connectionHTTP.c,v 1.13.2.5 2010/07/22 14:18:36 schmirl Exp $
*/
#include <ctype.h>
#include "server/connectionHTTP.h"
#include "server/menuHTTP.h"
#include "server/server.h"
#include "server/setup.h"
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()
@@ -26,28 +27,79 @@ cConnectionHTTP::~cConnectionHTTP()
delete m_LiveStreamer;
}
bool cConnectionHTTP::CanAuthenticate(void)
{
return opt_auth != NULL;
}
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);
else if (isspace(*Cmd)) {
; //TODO: multi-line header
}
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
if (*Cmd == '\0')
return true;
break;
}
return false; // ??? shouldn't happen
@@ -55,29 +107,59 @@ 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 (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;
if (!StreamdevHosts.Acceptable(RemoteIpAddr())) {
bool authOk = opt_auth && !m_Authorization.empty();
if (authOk) {
tStrStrMap::const_iterator it = Headers().find(AUTH_TYPE);
case hjTransfer:
if (m_Channel == NULL) {
DeferClose();
return Respond("HTTP/1.0 404 not found");
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")
&& Respond("WWW-authenticate: basic Realm=\"Streamdev-Server\")")
&& Respond("");
}
}
m_LiveStreamer = new cStreamdevLiveStreamer(0, m_StreamerParameter);
tStrStrMap::const_iterator it_method = Headers().find(REQUEST_METHOD);
tStrStrMap::const_iterator it_pathinfo = Headers().find(PATH_INFO);
if (it_method == Headers().end() || it_pathinfo == Headers().end()) {
// should never happen
esyslog("streamdev-server connectionHTTP: Missing method or pathinfo");
} else if (it_method->second.compare("GET") == 0 && ProcessURI(it_pathinfo->second)) {
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())
@@ -88,12 +170,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 (it_method->second.compare("HEAD") == 0 && ProcessURI(it_pathinfo->second)) {
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();
@@ -108,78 +224,109 @@ 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();
tStrStrMap::const_iterator it_query = Headers().find(QUERY_STRING);
const std::string& query = it_query == Headers().end() ? "" : it_query->second;
// 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;
@@ -189,80 +336,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(1, 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;
}

View File

@@ -1,5 +1,5 @@
/*
* $Id: connectionHTTP.h,v 1.5 2008/03/28 15:11:40 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,25 +24,19 @@ private:
hsFinished,
};
enum eHTTPJob {
hjTransfer,
hjListing,
};
std::string m_Request;
std::string m_Host;
//std::map<std::string,std::string> m_Headers; TODO: later?
std::string m_Authorization;
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);
@@ -52,8 +47,9 @@ public:
virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
virtual bool CanAuthenticate(void);
virtual bool Command(char *Cmd);
bool CmdGET(const std::string &Opts);
virtual bool Abort(void) const;
virtual void Flushed(void);

64
server/connectionIGMP.c Normal file
View File

@@ -0,0 +1,64 @@
/*
* $Id: connectionIGMP.c,v 1.1.2.3 2010/07/19 13:50:14 schmirl Exp $
*/
#include <ctype.h>
#include "server/connectionIGMP.h"
#include "server/server.h"
#include "server/setup.h"
#include <vdr/channels.h>
cConnectionIGMP::cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType) :
cServerConnection(Name, SOCK_DGRAM),
m_LiveStreamer(NULL),
m_ClientPort(ClientPort),
m_StreamType(StreamType)
{
}
cConnectionIGMP::~cConnectionIGMP()
{
delete m_LiveStreamer;
}
bool cConnectionIGMP::Start(cChannel *Channel, in_addr_t Dst)
{
if (Channel != NULL) {
cDevice *device = GetDevice(Channel, 0);
if (device != NULL) {
device->SwitchChannel(Channel, false);
struct in_addr ip;
ip.s_addr = Dst;
if (Connect(inet_ntoa(ip), m_ClientPort)) {
m_LiveStreamer = new cStreamdevLiveStreamer(0, this);
if (m_LiveStreamer->SetChannel(Channel, m_StreamType)) {
m_LiveStreamer->SetDevice(device);
if (!SetDSCP())
LOG_ERROR_STR("unable to set DSCP sockopt");
Dprintf("streamer start\n");
m_LiveStreamer->Start(this);
return true;
}
else
esyslog("streamdev-server IGMP: SetDevice failed");
DELETENULL(m_LiveStreamer);
}
else
esyslog("streamdev-server IGMP: Connect failed: %m");
}
else
esyslog("streamdev-server IGMP: GetDevice failed");
}
else
esyslog("streamdev-server IGMP: Channel not found");
return false;
}
void cConnectionIGMP::Stop()
{
if (m_LiveStreamer) {
m_LiveStreamer->Stop();
DELETENULL(m_LiveStreamer);
}
}

45
server/connectionIGMP.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* $Id: connectionIGMP.h,v 1.1.2.2 2009/02/13 10:39:42 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H
#define VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H
#include "connection.h"
#include "server/livestreamer.h"
#include <tools/select.h>
#define MULTICAST_PRIV_MIN ((uint32_t) 0xefff0000)
#define MULTICAST_PRIV_MAX ((uint32_t) 0xeffffeff)
class cStreamdevLiveStreamer;
class cConnectionIGMP: public cServerConnection {
private:
cStreamdevLiveStreamer *m_LiveStreamer;
int m_ClientPort;
eStreamType m_StreamType;
public:
cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType);
virtual ~cConnectionIGMP();
bool Start(cChannel *Channel, in_addr_t Dst);
void Stop();
/* Not used here */
virtual bool Command(char *Cmd) { return false; }
virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
virtual bool Abort(void) const;
};
inline bool cConnectionIGMP::Abort(void) const
{
return !m_LiveStreamer || m_LiveStreamer->Abort();
}
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
#include "server/connection.h"
#include "server/recplayer.h"
class cTBSocket;
class cStreamdevLiveStreamer;
@@ -9,28 +10,37 @@ class cStreamdevFilterStreamer;
class cLSTEHandler;
class cLSTCHandler;
class cLSTTHandler;
class cLSTRHandler;
class cConnectionVTP: public cServerConnection {
friend class cLSTEHandler;
// if your compiler doesn't understand the following statement
// (e.g. gcc 2.x), simply remove it and try again ;-)
#if !defined __GNUC__ || __GNUC__ >= 3
using cServerConnection::Respond;
#endif
private:
cTBSocket *m_LiveSocket;
cStreamdevLiveStreamer *m_LiveStreamer;
cTBSocket *m_FilterSocket;
cStreamdevFilterStreamer *m_FilterStreamer;
cTBSocket *m_RecSocket;
cTBSocket *m_DataSocket;
char *m_LastCommand;
eStreamType m_StreamType;
bool m_FiltersSupport;
RecPlayer *m_RecPlayer;
// Priority is only known in PROV command
// Store in here for later use in TUNE call
const cChannel *m_TuneChannel;
int m_TunePriority;
// Members adopted for SVDRP
cRecordings Recordings;
cLSTEHandler *m_LSTEHandler;
cLSTCHandler *m_LSTCHandler;
cLSTTHandler *m_LSTTHandler;
cLSTRHandler *m_LSTRHandler;
protected:
template<class cHandler>
@@ -51,7 +61,10 @@ public:
bool CmdCAPS(char *Opts);
bool CmdPROV(char *Opts);
bool CmdPORT(char *Opts);
bool CmdREAD(char *Opts);
bool CmdTUNE(char *Opts);
bool CmdPLAY(char *Opts);
bool CmdPRIO(char *Opts);
bool CmdADDP(char *Opts);
bool CmdDELP(char *Opts);
bool CmdADDF(char *Opts);
@@ -64,14 +77,20 @@ public:
bool CmdLSTE(char *Opts);
bool CmdLSTC(char *Opts);
bool CmdLSTT(char *Opts);
bool CmdLSTR(char *Opts);
// Commands adopted from SVDRP
bool CmdSTAT(const char *Option);
bool CmdMODT(const char *Option);
bool CmdNEWT(const char *Option);
bool CmdDELT(const char *Option);
//bool CmdLSTR(char *Opts);
//bool CmdDELR(char *Opts);
bool CmdNEXT(const char *Option);
bool CmdNEWC(const char *Option);
bool CmdMODC(const char *Option);
bool CmdMOVC(const char *Option);
bool CmdDELC(const char *Option);
bool CmdDELR(const char *Option);
bool CmdRENR(const char *Option);
bool Respond(int Code, const char *Message, ...)
__attribute__ ((format (printf, 3, 4)));

View File

@@ -1,14 +1,11 @@
/*
* $Id: livefilter.c,v 1.5 2008/04/07 14:27:31 schmirl Exp $
* $Id: livefilter.c,v 1.5.2.2 2009/02/13 13:02:34 schmirl Exp $
*/
#include "server/livefilter.h"
#include "server/streamer.h"
#include "common.h"
#ifndef TS_SIZE
# define TS_SIZE 188
#endif
#ifndef TS_SYNC_BYTE
# define TS_SYNC_BYTE 0x47
#endif
@@ -29,6 +26,7 @@ void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data,
buffer[1] = ((Pid >> 8) & 0x3f) | (pos==0 ? 0x40 : 0); /* bit 6: payload unit start indicator (PUSI) */
buffer[2] = Pid & 0xff;
buffer[3] = Tid;
// this makes it a proprietary stream
buffer[4] = (uchar)chunk;
memcpy(buffer + 5, Data + pos, chunk);
length -= chunk;

View File

@@ -8,11 +8,12 @@
#include "server/livestreamer.h"
#include "server/livefilter.h"
#include "remux/ts2ps.h"
#include "remux/ts2pes.h"
#include "remux/ts2es.h"
#include "remux/extern.h"
#include "common.h"
#define TSPATREPACKER
using namespace Streamdev;
// --- cStreamdevLiveReceiver -------------------------------------------------
@@ -73,6 +74,8 @@ private:
int pmtPid;
int pmtSid;
int pmtVersion;
uchar tspat_buf[TS_SIZE];
cStreamdevBuffer siBuffer;
const cChannel *m_Channel;
cStreamdevLiveStreamer *m_Streamer;
@@ -82,11 +85,13 @@ private:
int GetPid(SI::PMT::Stream& stream);
public:
cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel);
uchar* Get(int &Count) { return siBuffer.Get(Count); }
void Del(int Count) { return siBuffer.Del(Count); }
};
cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel)
cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel): siBuffer(10 * TS_SIZE, TS_SIZE)
{
Dprintf("cStreamdevPatFilter(\"%s\")", Channel->Name());
Dprintf("cStreamdevPatFilter(\"%s\")\n", Channel->Name());
assert(Streamer);
m_Channel = Channel;
m_Streamer = Streamer;
@@ -94,6 +99,29 @@ cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const
pmtSid = 0;
pmtVersion = -1;
Set(0x00, 0x00); // PAT
// initialize PAT buffer. Only some values are dynamic (see comments)
memset(tspat_buf, 0xff, TS_SIZE);
tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h)
tspat_buf[1] = 0x40; // Set payload unit start indicator bit
tspat_buf[2] = 0x0; // PID
tspat_buf[3] = 0x10; // Set payload flag, DYNAMIC: Continuity counter
tspat_buf[4] = 0x0; // SI pointer field
tspat_buf[5] = 0x0; // PAT table id
tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set
tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1
tspat_buf[8] = 0; // DYNAMIC: Transport stream ID (bits 8-15)
tspat_buf[9] = 0; // DYNAMIC: Transport stream ID (bits 0-7)
tspat_buf[10] = 0xc0; // Reserved, DYNAMIC: Version number, DYNAMIC: Current next indicator
tspat_buf[11] = 0x0; // Section number
tspat_buf[12] = 0x0; // Last section number
tspat_buf[13] = 0; // DYNAMIC: Program number (bits 8-15)
tspat_buf[14] = 0; // DYNAMIC: Program number (bits 0-7)
tspat_buf[15] = 0xe0; // Reserved, DYNAMIC: Network ID (bits 8-12)
tspat_buf[16] = 0; // DYNAMIC: Network ID (bits 0-7)
tspat_buf[17] = 0; // DYNAMIC: Checksum
tspat_buf[18] = 0; // DYNAMIC: Checksum
tspat_buf[19] = 0; // DYNAMIC: Checksum
tspat_buf[20] = 0; // DYNAMIC: Checksum
}
static const char * const psStreamTypes[] = {
@@ -145,7 +173,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
case 0x10: // ISO/IEC 14496-2 Visual (MPEG-4)
case 0x11: // ISO/IEC 14496-3 Audio with LATM transport syntax
case 0x1b: // ISO/IEC 14496-10 Video (MPEG-4 part 10/AVC, aka H.264)
Dprintf("cStreamdevPatFilter PMT scanner adding PID %d (%s)",
Dprintf("cStreamdevPatFilter PMT scanner adding PID %d (%s)\n",
stream.getPid(), psStreamTypes[stream.getStreamType()]);
return stream.getPid();
case 0x05: // ISO/IEC 13818-1 private sections
@@ -153,19 +181,22 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) {
switch (d->getDescriptorTag()) {
case SI::AC3DescriptorTag:
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
stream.getPid(), psStreamTypes[stream.getStreamType()], "AC3");
delete d;
return stream.getPid();
case SI::TeletextDescriptorTag:
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
stream.getPid(), psStreamTypes[stream.getStreamType()], "Teletext");
delete d;
return stream.getPid();
case SI::SubtitlingDescriptorTag:
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s",
Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n",
stream.getPid(), psStreamTypes[stream.getStreamType()], "DVBSUB");
delete d;
return stream.getPid();
default:
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s",
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n",
stream.getPid(), psStreamTypes[stream.getStreamType()], "UNKNOWN");
break;
}
@@ -195,6 +226,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
stream.getPid(), stream.getStreamType(),
d->getLength(), rawdata[2], rawdata[3],
rawdata[4], rawdata[5]);
delete d;
return stream.getPid();
}
}
@@ -210,7 +242,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
return stream.getPid();
}
}
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s",
Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n",
stream.getPid(), psStreamTypes[stream.getStreamType()<0x1c?stream.getStreamType():0], "UNKNOWN");
break;
}
@@ -220,7 +252,7 @@ int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream)
void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
{
if (Pid == 0x00) {
if (Tid == 0x00 && !pmtPid) {
if (Tid == 0x00) {
SI::PAT pat(Data, false);
if (!pat.CheckCRCAndParse())
return;
@@ -229,58 +261,46 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
if (!assoc.isNITPid()) {
const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId());
if (Channel && (Channel == m_Channel)) {
int prevPmtPid = pmtPid;
if (0 != (pmtPid = assoc.getPid())) {
Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d", Channel->Name(), pmtPid);
Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d\n", Channel->Name(), pmtPid);
pmtSid = assoc.getServiceId();
if (Length < TS_SIZE-5) {
// repack PAT to TS frame and send to client
#ifndef TSPATREPACKER
uint8_t pat_ts[TS_SIZE] = {TS_SYNC_BYTE, 0x40 /* pusi=1 */, 0 /* pid=0 */, 0x10 /* adaption=1 */, 0 /* pointer */};
memcpy(pat_ts + 5, Data, Length);
m_Streamer->Put(pat_ts, TS_SIZE);
#else
int ts_id;
unsigned int crc, i, len;
uint8_t *tmp, tspat_buf[TS_SIZE];
memset(tspat_buf, 0xff, TS_SIZE);
memset(tspat_buf, 0x0, 4 + 12 + 5); // TS_HDR_LEN + PAT_TABLE_LEN + 5
ts_id = Channel->Tid(); // Get transport stream id of the channel
tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h)
tspat_buf[1] = 0x40; // Set payload unit start indicator bit
tspat_buf[2] = 0x0; // PID
tspat_buf[3] = 0x10; // Set payload flag to indicate precence of payload data
tspat_buf[4] = 0x0; // PSI
tspat_buf[5] = 0x0; // PAT table id
tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set
tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1
tspat_buf[8] = (ts_id >> 8) & 0xff; // Transport stream ID (bits 8-15)
tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7)
tspat_buf[10] = 0x01; // Version number 0, Current next indicator bit set
tspat_buf[11] = 0x0; // Section number
tspat_buf[12] = 0x0; // Last section number
tspat_buf[13] = (pmtSid >> 8) & 0xff; // Program number (bits 8-15)
tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7)
tspat_buf[15] = (pmtPid >> 8) & 0xff; // Network ID (bits 8-12)
tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7)
crc = 0xffffffff;
len = 12; // PAT_TABLE_LEN
tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1
while (len--) {
crc ^= *tmp++ << 24;
for (i = 0; i < 8; i++)
crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY
}
tspat_buf[17] = crc >> 24 & 0xff; // Checksum
tspat_buf[18] = crc >> 16 & 0xff; // Checksum
tspat_buf[19] = crc >> 8 & 0xff; // Checksum
tspat_buf[20] = crc & 0xff; // Checksum
m_Streamer->Put(tspat_buf, TS_SIZE);
#endif
} else
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
m_Streamer->SetPids(pmtPid);
Add(pmtPid, 0x02);
pmtVersion = -1;
// repack PAT to TS frame and send to client
int ts_id;
unsigned int crc, i, len;
uint8_t *tmp;
static uint8_t ccounter = 0;
ccounter = (ccounter + 1) % 16;
ts_id = Channel->Tid(); // Get transport stream id of the channel
tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter
tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15)
tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7)
tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) |
pat.getCurrentNextIndicator();// Version number, Current next indicator
tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15)
tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7)
tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12)
tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7)
crc = 0xffffffff;
len = 12; // PAT_TABLE_LEN
tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1
while (len--) {
crc ^= *tmp++ << 24;
for (i = 0; i < 8; i++)
crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY
}
tspat_buf[17] = crc >> 24 & 0xff; // Checksum
tspat_buf[18] = crc >> 16 & 0xff; // Checksum
tspat_buf[19] = crc >> 8 & 0xff; // Checksum
tspat_buf[20] = crc & 0xff; // Checksum
int written = siBuffer.PutTS(tspat_buf, TS_SIZE);
if (written != TS_SIZE)
siBuffer.ReportOverflow(TS_SIZE - written);
if (pmtPid != prevPmtPid) {
m_Streamer->SetPid(pmtPid, true);
Add(pmtPid, 0x02);
pmtVersion = -1;
}
return;
}
}
@@ -295,8 +315,8 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
return; // skip broken PMT records
if (pmtVersion != -1) {
if (pmtVersion != pmt.getVersionNumber()) {
Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids");
Del(pmtPid, 0x02);
Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n");
cFilter::Del(pmtPid, 0x02);
pmtPid = 0; // this triggers PAT scan
}
return;
@@ -323,20 +343,16 @@ 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),
m_Device(NULL),
m_Receiver(NULL),
m_PatFilter(NULL),
m_PESRemux(NULL),
m_ESRemux(NULL),
m_PSRemux(NULL),
m_ExtRemux(NULL)
m_Remux(NULL)
{
}
@@ -349,10 +365,7 @@ cStreamdevLiveStreamer::~cStreamdevLiveStreamer()
DELETENULL(m_PatFilter);
}
DELETENULL(m_Receiver);
delete m_PESRemux;
delete m_ESRemux;
delete m_PSRemux;
delete m_ExtRemux;
delete m_Remux;
}
bool cStreamdevLiveStreamer::HasPid(int Pid)
@@ -429,148 +442,121 @@ bool cStreamdevLiveStreamer::SetPids(int Pid, const int *Pids1, const int *Pids2
return true;
}
void cStreamdevLiveStreamer::SetPriority(int Priority)
{
m_Priority = Priority;
StartReceiver();
}
void cStreamdevLiveStreamer::StartReceiver(void)
{
DELETENULL(m_Receiver);
if (m_NumPids > 0) {
Dprintf("Creating Receiver to respect changed pids\n");
cReceiver *current = m_Receiver;
#if VDRVERSNUM < 10500
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->Ca(), m_Priority, m_Pids);
#else
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->GetChannelID(), m_Priority, m_Pids);
#endif
if (IsRunning() && m_Device != NULL) {
Dprintf("Attaching new receiver\n");
cThreadLock ThreadLock(m_Device);
if (IsRunning())
Attach();
}
delete current;
}
else
DELETENULL(m_Receiver);
}
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;
m_ESRemux = new cTS2ESRemux(pid);
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_PESRemux = new cRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids(), false);
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_PSRemux = 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 stEXT:
m_Remux = new cExternRemux(Connection(), m_Channel, Apids, Dpids);
// fall through
case stTS:
// This should never happen, but ...
if (m_PatFilter) {
Detach();
DELETENULL(m_PatFilter);
}
// Set pids from cChannel
SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
if (m_Channel->Vpid() != m_Channel->Ppid())
SetPid(m_Channel->Ppid(), true);
// Set pids from PMT
m_PatFilter = new cStreamdevPatFilter(this, m_Channel);
return true;
case stExtern:
m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids(), m_Parameter);
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
case stTSPIDS:
Dprintf("pid streaming mode\n");
return true;
default:
return false;
}
return false;
}
int cStreamdevLiveStreamer::Put(const uchar *Data, int Count)
{
switch (m_StreamType) {
case stTS:
case stTSPIDS:
return cStreamdevStreamer::Put(Data, Count);
case stPES:
return m_PESRemux->Put(Data, Count);
case stES:
return m_ESRemux->Put(Data, Count);
case stPS:
return m_PSRemux->Put(Data, Count);
case stExtern:
return m_ExtRemux->Put(Data, Count);
default: // shouldn't happen???
return 0;
// insert si data
if (m_PatFilter) {
int siCount;
uchar *siData = m_PatFilter->Get(siCount);
if (siData) {
if (m_Remux)
siCount = m_Remux->Put(siData, siCount);
else
siCount = cStreamdevStreamer::Put(siData, siCount);
if (siCount)
m_PatFilter->Del(siCount);
}
}
if (m_Remux)
return m_Remux->Put(Data, Count);
else
return cStreamdevStreamer::Put(Data, Count);
}
uchar *cStreamdevLiveStreamer::Get(int &Count)
{
switch (m_StreamType) {
case stTS:
case stTSPIDS:
if (m_Remux)
return m_Remux->Get(Count);
else
return cStreamdevStreamer::Get(Count);
case stPES:
return m_PESRemux->Get(Count);
case stES:
return m_ESRemux->Get(Count);
case stPS:
return m_PSRemux->Get(Count);
case stExtern:
return m_ExtRemux->Get(Count);
default: // shouldn't happen???
return 0;
}
}
void cStreamdevLiveStreamer::Del(int Count)
{
switch (m_StreamType) {
case stTS:
case stTSPIDS:
if (m_Remux)
m_Remux->Del(Count);
else
cStreamdevStreamer::Del(Count);
break;
case stPES:
m_PESRemux->Del(Count);
break;
case stES:
m_ESRemux->Del(Count);
break;
case stPS:
m_PSRemux->Del(Count);
break;
case stExtern:
m_ExtRemux->Del(Count);
break;
}
}
void cStreamdevLiveStreamer::Attach(void)

View File

@@ -7,10 +7,9 @@
#include "server/streamer.h"
#include "common.h"
class cTS2PSRemux;
class cTS2ESRemux;
class cExternRemux;
class cRemux;
namespace Streamdev {
class cTSRemux;
}
class cStreamdevPatFilter;
class cStreamdevLiveReceiver;
@@ -19,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;
@@ -27,22 +25,20 @@ private:
cDevice *m_Device;
cStreamdevLiveReceiver *m_Receiver;
cStreamdevPatFilter *m_PatFilter;
cRemux *m_PESRemux;
cTS2ESRemux *m_ESRemux;
cTS2PSRemux *m_PSRemux;
cExternRemux *m_ExtRemux;
Streamdev::cTSRemux *m_Remux;
void StartReceiver(void);
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);
void SetPriority(int Priority);
virtual int Put(const uchar *Data, int Count);
virtual uchar *Get(int &Count);

View File

@@ -2,7 +2,7 @@
#include "server/menuHTTP.h"
//**************************** cChannelIterator **************
cChannelIterator::cChannelIterator(cChannel *First): channel(First)
cChannelIterator::cChannelIterator(const cChannel *First): channel(First)
{}
const cChannel* cChannelIterator::Next()
@@ -19,7 +19,7 @@ cListAll::cListAll(): cChannelIterator(Channels.First())
const cChannel* cListAll::NextChannel(const cChannel *Channel)
{
if (Channel)
Channel = Channels.Next(Channel);
Channel = SkipFakeGroups(Channels.Next(Channel));
return Channel;
}
@@ -46,14 +46,19 @@ const cChannel* cListGroups::NextChannel(const cChannel *Channel)
}
//
// ********************* cListGroup ****************
cListGroup::cListGroup(const cChannel *Group): cChannelIterator((Group && Group->GroupSep() && Channels.Next(Group) && !Channels.Next(Group)->GroupSep()) ? Channels.Next(Group) : NULL)
cListGroup::cListGroup(const cChannel *Group): cChannelIterator(GetNextChannelInGroup(Group))
{}
const cChannel* cListGroup::GetNextChannelInGroup(const cChannel *Channel)
{
if (Channel)
Channel = SkipFakeGroups(Channels.Next(Channel));
return Channel && !Channel->GroupSep() ? Channel : NULL;
}
const cChannel* cListGroup::NextChannel(const cChannel *Channel)
{
if (Channel)
Channel = Channels.Next(Channel);
return (Channel && !Channel->GroupSep()) ? Channel : NULL;
return GetNextChannelInGroup(Channel);
}
//
// ********************* cListTree ****************
@@ -68,7 +73,7 @@ const cChannel* cListTree::NextChannel(const cChannel *Channel)
if (currentGroup == selectedGroup)
{
if (Channel)
Channel = Channels.Next(Channel);
Channel = SkipFakeGroups(Channels.Next(Channel));
if (Channel && Channel->GroupSep())
currentGroup = Channel;
}
@@ -112,10 +117,10 @@ const cChannel* cChannelList::GetGroup(int Index)
// ******************** cHtmlChannelList ******************
const char* cHtmlChannelList::menu =
"[<a href=\"/\">Home</a> (<a href=\"all.html\">no script</a>)] "
"[<a href=\"tree.html\">Tree View</a>] "
"[<a href=\"groups.html\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] "
"[<a href=\"channels.html\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] ";
"[<a href=\"/\">Home</a> (<a href=\"all.html\" tvid=\"RED\">no script</a>)] "
"[<a href=\"tree.html\" tvid=\"GREEN\">Tree View</a>] "
"[<a href=\"groups.html\" tvid=\"YELLOW\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] "
"[<a href=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] ";
const char* cHtmlChannelList::css =
"<style type=\"text/css\">\n"
@@ -205,8 +210,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;
}
@@ -336,9 +341,24 @@ std::string cHtmlChannelList::GroupTitle()
std::string cHtmlChannelList::ItemText()
{
std::string line;
std::string suffix;
switch (streamType) {
case stTS: suffix = (std::string) ".ts"; break;
case stPS: suffix = (std::string) ".vob"; break;
// for Network Media Tank
case stPES: suffix = (std::string) ".vdr"; break;
default: suffix = "";
}
line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";
line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + "\">" +
current->Name() + "</a>";
line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + suffix + "\"";
// for Network Media Tank
line += (std::string) " vod ";
if (current->Number() < 1000)
line += (std::string) " tvid=\"" + (const char*) itoa(current->Number()) + "\"";
line += (std::string) ">" + current->Name() + "</a>";
int count = 0;
for (int i = 0; current->Apid(i) != 0; ++i, ++count)
@@ -351,11 +371,11 @@ std::string cHtmlChannelList::ItemText()
int index = 1;
for (int i = 0; current->Apid(i) != 0; ++i, ++index) {
line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
"+" + (const char*)itoa(index) + "\" class=\"apid\">" + current->Alang(i) + "</a>";
"+" + (const char*)itoa(index) + suffix + "\" class=\"apid\" vod>" + current->Alang(i) + "</a>";
}
for (int i = 0; current->Dpid(i) != 0; ++i, ++index) {
line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() +
"+" + (const char*)itoa(index) + "\" class=\"dpid\">" + current->Dlang(i) + "</a>";
"+" + (const char*)itoa(index) + suffix + "\" class=\"dpid\" vod>" + current->Dlang(i) + "</a>";
}
}
line += "</li>";
@@ -406,13 +426,13 @@ std::string cM3uChannelList::Next()
if (channel->GroupSep())
{
return (std::string) "#EXTINF:0," + name + "\r\n" +
return (std::string) "#EXTINF:-1," + name + "\r\n" +
base + "group.m3u?group=" +
(const char*) itoa(cChannelList::GetGroupIndex(channel));
}
else
{
return (std::string) "#EXTINF:0," +
return (std::string) "#EXTINF:-1," +
(const char*) itoa(channel->Number()) + " " + name + "\r\n" +
base + (std::string) channel->GetChannelID().ToString();
}

View File

@@ -13,9 +13,10 @@ class cChannelIterator
const cChannel *channel;
protected:
virtual const cChannel* NextChannel(const cChannel *Channel) = 0;
static inline const cChannel* SkipFakeGroups(const cChannel *Channel);
public:
const cChannel* Next();
cChannelIterator(cChannel *First);
cChannelIterator(const cChannel *First);
virtual ~cChannelIterator() {};
};
@@ -48,6 +49,8 @@ class cListGroups: public cChannelIterator
class cListGroup: public cChannelIterator
{
private:
static const cChannel* GetNextChannelInGroup(const cChannel *Channel);
protected:
virtual const cChannel* NextChannel(const cChannel *Channel);
public:
@@ -113,7 +116,16 @@ class cHtmlChannelList: public cChannelList
std::string ItemText();
std::string PageBottom();
public:
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: text/html\r\n\r\n"; }
virtual std::string HttpHeader() {
return cChannelList::HttpHeader()
+ "Content-type: text/html; charset="
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
+ (cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8")
#else
+ I18nCharSets()[Setup.OSDLanguage]
#endif
+ "\r\n";
}
virtual bool HasNext();
virtual std::string Next();
cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget);
@@ -130,11 +142,27 @@ class cM3uChannelList: public cChannelList
cCharSetConv m_IConv;
#endif
public:
virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: audio/x-mpegurl\r\n"; };
virtual std::string HttpHeader() {
return cChannelList::HttpHeader()
+ "Content-type: audio/x-mpegurl; charset="
#if defined(APIVERSNUM) && APIVERSNUM >= 10503
+ "UTF-8"
#else
+ I18nCharSets()[Setup.OSDLanguage]
#endif
+ "\r\n";
}
virtual bool HasNext();
virtual std::string Next();
cM3uChannelList(cChannelIterator *Iterator, const char* Base);
virtual ~cM3uChannelList();
};
inline const cChannel* cChannelIterator::SkipFakeGroups(const cChannel* Group)
{
while (Group && Group->GroupSep() && !*Group->Name())
Group = Channels.Next(Group);
return Group;
}
#endif

288
server/recplayer.c Normal file
View File

@@ -0,0 +1,288 @@
/*
Copyright 2004-2005 Chris Tallon
This file is part of VOMP.
and adopted for streamdev to play recordings
VOMP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
VOMP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with VOMP; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "recplayer.h"
#define _XOPEN_SOURCE 600
#include <fcntl.h>
RecPlayer::RecPlayer(cRecording* rec)
{
file = NULL;
fileOpen = 0;
lastPosition = 0;
recording = rec;
for(int i = 1; i < 1000; i++) segments[i] = NULL;
// FIXME find out max file path / name lengths
#if VDRVERSNUM >= 10703
indexFile = new cIndexFile(recording->FileName(), false, rec->IsPesRecording());
#else
indexFile = new cIndexFile(recording->FileName(), false);
#endif
if (!indexFile) esyslog("ERROR: Streamdev: Failed to create indexfile!");
scan();
}
void RecPlayer::scan()
{
if (file) fclose(file);
totalLength = 0;
fileOpen = 0;
totalFrames = 0;
int i = 1;
while(segments[i++]) delete segments[i];
char fileName[2048];
for(i = 1; i < 1000; i++)
{
#if APIVERSNUM < 10703
snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i);
//log->log("RecPlayer", Log::DEBUG, "FILENAME: %s", fileName);
file = fopen(fileName, "r");
#else
snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i);
file = fopen(fileName, "r");
if (!file) {
snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i);
file = fopen(fileName, "r");
}
#endif
if (!file) break;
segments[i] = new Segment();
segments[i]->start = totalLength;
fseek(file, 0, SEEK_END);
totalLength += ftell(file);
totalFrames = indexFile->Last();
//log->log("RecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames);
segments[i]->end = totalLength;
fclose(file);
}
file = NULL;
}
RecPlayer::~RecPlayer()
{
//log->log("RecPlayer", Log::DEBUG, "destructor");
int i = 1;
while(segments[i++]) delete segments[i];
if (file) fclose(file);
}
int RecPlayer::openFile(int index)
{
if (file) fclose(file);
char fileName[2048];
#if APIVERSNUM >= 10703
snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), index);
isyslog("openFile called for index %i string:%s", index, fileName);
file = fopen(fileName, "r");
if (file)
{
fileOpen = index;
return 1;
}
#endif
snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index);
isyslog("openFile called for index %i string:%s", index, fileName);
//log->log("RecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName);
file = fopen(fileName, "r");
if (file)
{
fileOpen = index;
return 1;
}
//log->log("RecPlayer", Log::DEBUG, "file failed to open");
fileOpen = 0;
return 0;
}
uint64_t RecPlayer::getLengthBytes()
{
return totalLength;
}
uint32_t RecPlayer::getLengthFrames()
{
return totalFrames;
}
unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount)
{
if ((amount > totalLength) || (amount > 500000))
{
//log->log("RecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount);
return 0;
}
if (position >= totalLength)
{
//log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!");
return 0;
}
if ((position + amount) > totalLength)
{
//log->log("RecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount");
amount = totalLength - position;
}
// work out what block position is in
int segmentNumber;
for(segmentNumber = 1; segmentNumber < 1000; segmentNumber++)
{
if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
// position is in this block
}
// we could be seeking around
if (segmentNumber != fileOpen)
{
if (!openFile(segmentNumber)) return 0;
}
uint64_t currentPosition = position;
uint32_t yetToGet = amount;
uint32_t got = 0;
uint32_t getFromThisSegment = 0;
uint32_t filePosition;
while(got < amount)
{
if (got)
{
// if(got) then we have already got some and we are back around
// advance the file pointer to the next file
if (!openFile(++segmentNumber)) return 0;
}
// is the request completely in this block?
if ((currentPosition + yetToGet) <= segments[segmentNumber]->end)
getFromThisSegment = yetToGet;
else
getFromThisSegment = segments[segmentNumber]->end - currentPosition;
filePosition = currentPosition - segments[segmentNumber]->start;
fseek(file, filePosition, SEEK_SET);
if (fread(&buffer[got], getFromThisSegment, 1, file) != 1) return 0; // umm, big problem.
// Tell linux not to bother keeping the data in the FS cache
posix_fadvise(file->_fileno, filePosition, getFromThisSegment, POSIX_FADV_DONTNEED);
got += getFromThisSegment;
currentPosition += getFromThisSegment;
yetToGet -= getFromThisSegment;
}
lastPosition = position;
return got;
}
uint64_t RecPlayer::getLastPosition()
{
return lastPosition;
}
cRecording* RecPlayer::getCurrentRecording()
{
return recording;
}
uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber)
{
if (!indexFile) return 0;
#if VDRVERSNUM >= 10703
uint16_t retFileNumber;
off_t retFileOffset;
#else
uchar retFileNumber;
int retFileOffset;
#endif
if (!indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset))
{
return 0;
}
// log->log("RecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset);
if (!segments[retFileNumber]) return 0;
uint64_t position = segments[retFileNumber]->start + retFileOffset;
// log->log("RecPlayer", Log::DEBUG, "Pos: %llu", position);
return position;
}
uint32_t RecPlayer::frameNumberFromPosition(uint64_t position)
{
if (!indexFile) return 0;
if (position >= totalLength)
{
//log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!");
return 0;
}
uint8_t segmentNumber;
for(segmentNumber = 1; segmentNumber < 255; segmentNumber++)
{
if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
// position is in this block
}
uint32_t askposition = position - segments[segmentNumber]->start;
return indexFile->Get((int)segmentNumber, askposition);
}
bool RecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength)
{
// 0 = backwards
// 1 = forwards
if (!indexFile) return false;
int iframeLength;
int indexReturnFrameNumber;
indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), NULL, NULL, &iframeLength);
//log->log("RecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength);
if (indexReturnFrameNumber == -1) return false;
*rfilePosition = positionFromFrameNumber(indexReturnFrameNumber);
*rframeNumber = (uint32_t)indexReturnFrameNumber;
*rframeLength = (uint32_t)iframeLength;
return true;
}

63
server/recplayer.h Normal file
View File

@@ -0,0 +1,63 @@
/*
Copyright 2004-2005 Chris Tallon
This file is part of VOMP.
VOMP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
VOMP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with VOMP; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef RECPLAYER_H
#define RECPLAYER_H
#include <stdio.h>
#include <vdr/recording.h>
#include "server/streamer.h"
class Segment
{
public:
uint64_t start;
uint64_t end;
};
class RecPlayer
{
public:
RecPlayer(cRecording* rec);
~RecPlayer();
uint64_t getLengthBytes();
uint32_t getLengthFrames();
unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount);
int openFile(int index);
uint64_t getLastPosition();
cRecording* getCurrentRecording();
void scan();
uint64_t positionFromFrameNumber(uint32_t frameNumber);
uint32_t frameNumberFromPosition(uint64_t position);
bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength);
private:
cRecording* recording;
cIndexFile* indexFile;
FILE* file;
int fileOpen;
Segment* segments[1000];
uint64_t totalLength;
uint64_t lastPosition;
uint32_t totalFrames;
};
#endif

View File

@@ -1,10 +1,11 @@
/*
* $Id: server.c,v 1.5 2007/04/02 10:32:34 schmirl Exp $
* $Id: server.c,v 1.5.2.5 2009/02/13 10:39:42 schmirl Exp $
*/
#include "server/server.h"
#include "server/componentVTP.h"
#include "server/componentHTTP.h"
#include "server/componentIGMP.h"
#include "server/setup.h"
#include <vdr/tools.h>
@@ -13,14 +14,15 @@
#include <errno.h>
cSVDRPhosts StreamdevHosts;
char *opt_auth = NULL;
char *opt_remux = NULL;
cStreamdevServer *cStreamdevServer::m_Instance = NULL;
cList<cServerComponent> cStreamdevServer::m_Servers;
cList<cServerConnection> cStreamdevServer::m_Clients;
cStreamdevServer::cStreamdevServer(void):
cThread("streamdev server"),
m_Active(false)
cThread("streamdev server")
{
Start();
}
@@ -35,6 +37,12 @@ void cStreamdevServer::Initialize(void)
if (m_Instance == NULL) {
if (StreamdevServerSetup.StartVTPServer) Register(new cComponentVTP);
if (StreamdevServerSetup.StartHTTPServer) Register(new cComponentHTTP);
if (StreamdevServerSetup.StartIGMPServer) {
if (strcmp(StreamdevServerSetup.IGMPBindIP, "0.0.0.0") == 0)
esyslog("streamdev-server: Not starting IGMP. IGMP must be bound to a local IP");
else
Register(new cComponentIGMP);
}
m_Instance = new cStreamdevServer;
}
@@ -47,10 +55,8 @@ void cStreamdevServer::Destruct(void)
void cStreamdevServer::Stop(void)
{
if (m_Active) {
m_Active = false;
if (Running())
Cancel(3);
}
}
void cStreamdevServer::Register(cServerComponent *Server)
@@ -60,8 +66,6 @@ void cStreamdevServer::Register(cServerComponent *Server)
void cStreamdevServer::Action(void)
{
m_Active = true;
/* Initialize Server components, deleting those that failed */
for (cServerComponent *c = m_Servers.First(); c;) {
cServerComponent *next = m_Servers.Next(c);
@@ -72,11 +76,15 @@ void cStreamdevServer::Action(void)
if (m_Servers.Count() == 0) {
esyslog("ERROR: no streamdev server activated, exiting");
m_Active = false;
#if VDRVERSNUM > 10403
Cancel(-1);
#else
return;
#endif
}
cTBSelect select;
while (m_Active) {
while (Running()) {
select.Clear();
/* Ask all Server components to register to the selector */
@@ -102,9 +110,9 @@ void cStreamdevServer::Action(void)
sel = 0;
}
}
} while (sel < 0 && errno == ETIMEDOUT && m_Active);
} while (sel < 0 && errno == ETIMEDOUT && Running());
if (!m_Active)
if (!Running())
break;
if (sel < 0) {
esyslog("fatal error, server exiting: %m");
@@ -115,13 +123,15 @@ void cStreamdevServer::Action(void)
for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c)){
if (sel && select.CanRead(c->Socket())) {
cServerConnection *client = c->Accept();
if (!client)
continue;
m_Clients.Add(client);
if (m_Clients.Count() > StreamdevServerSetup.MaxClients) {
esyslog("streamdev: too many clients, rejecting %s:%d",
client->RemoteIp().c_str(), client->RemotePort());
client->Reject();
} else if (!StreamdevHosts.Acceptable(client->RemoteIpAddr())) {
} else if (!client->CanAuthenticate() && !StreamdevHosts.Acceptable(client->RemoteIpAddr())) {
esyslog("streamdev: client %s:%d not allowed to connect",
client->RemoteIp().c_str(), client->RemotePort());
client->Reject();
@@ -164,6 +174,4 @@ void cStreamdevServer::Action(void)
c->Destruct();
m_Servers.Del(c);
}
m_Active = false;
}

View File

@@ -1,5 +1,5 @@
/*
* $Id: server.h,v 1.3 2008/04/07 14:50:33 schmirl Exp $
* $Id: server.h,v 1.3.2.3 2008/10/22 11:59:37 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_SERVER_H
@@ -10,13 +10,14 @@
#include "server/component.h"
#include "server/connection.h"
#define EXTERNREMUXPATH (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "externremux.sh"))
#define DEFAULT_EXTERNREMUX (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "externremux.sh"))
#define STREAMDEVHOSTSPATH (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "streamdevhosts.conf"))
extern char *opt_auth;
extern char *opt_remux;
class cStreamdevServer: public cThread {
private:
bool m_Active;
static cStreamdevServer *m_Instance;
static cList<cServerComponent> m_Servers;
static cList<cServerConnection> m_Clients;

View File

@@ -1,5 +1,5 @@
/*
* $Id: setup.c,v 1.3 2008/04/07 14:50:33 schmirl Exp $
* $Id: setup.c,v 1.3.2.4 2010/07/19 13:50:14 schmirl Exp $
*/
#include <vdr/menuitems.h>
@@ -17,10 +17,14 @@ cStreamdevServerSetup::cStreamdevServerSetup(void) {
StartHTTPServer = true;
HTTPServerPort = 3000;
HTTPStreamType = stPES;
StartIGMPServer = false;
IGMPClientPort = 1234;
IGMPStreamType = stTS;
SuspendMode = smAlways;
AllowSuspend = false;
strcpy(VTPBindIP, "0.0.0.0");
strcpy(HTTPBindIP, "0.0.0.0");
strcpy(IGMPBindIP, "0.0.0.0");
}
bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) {
@@ -32,37 +36,82 @@ bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) {
else if (strcmp(Name, "HTTPServerPort") == 0) HTTPServerPort = atoi(Value);
else if (strcmp(Name, "HTTPStreamType") == 0) HTTPStreamType = atoi(Value);
else if (strcmp(Name, "HTTPBindIP") == 0) strcpy(HTTPBindIP, Value);
else if (strcmp(Name, "StartIGMPServer") == 0) StartIGMPServer = atoi(Value);
else if (strcmp(Name, "IGMPClientPort") == 0) IGMPClientPort = atoi(Value);
else if (strcmp(Name, "IGMPStreamType") == 0) IGMPStreamType = atoi(Value);
else if (strcmp(Name, "IGMPBindIP") == 0) strcpy(IGMPBindIP, Value);
else if (strcmp(Name, "SuspendMode") == 0) SuspendMode = atoi(Value);
else if (strcmp(Name, "AllowSuspend") == 0) AllowSuspend = atoi(Value);
else return false;
return true;
}
const char* cStreamdevServerMenuSetupPage::StreamTypes[st_Count - 1] = {
"TS",
"PES",
"PS",
"ES",
"EXT"
};
const char* cStreamdevServerMenuSetupPage::SuspendModes[sm_Count] = {
"Offer suspend mode",
"Always suspended",
"Never suspended"
};
cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) {
m_NewSetup = StreamdevServerSetup;
AddCategory (tr("Common Settings"));
AddRangeEdit(tr("Maximum Number of Clients"), m_NewSetup.MaxClients, 0, 100);
AddSuspEdit (tr("Suspend behaviour"), m_NewSetup.SuspendMode);
AddBoolEdit (tr("Client may suspend"), m_NewSetup.AllowSuspend);
AddCategory (tr("VDR-to-VDR Server"));
AddBoolEdit (tr("Start VDR-to-VDR Server"), m_NewSetup.StartVTPServer);
AddShortEdit(tr("VDR-to-VDR Server Port"), m_NewSetup.VTPServerPort);
AddIpEdit (tr("Bind to IP"), m_NewSetup.VTPBindIP);
AddCategory (tr("HTTP Server"));
AddBoolEdit (tr("Start HTTP Server"), m_NewSetup.StartHTTPServer);
AddShortEdit(tr("HTTP Server Port"), m_NewSetup.HTTPServerPort);
AddTypeEdit (tr("HTTP Streamtype"), m_NewSetup.HTTPStreamType);
AddIpEdit (tr("Bind to IP"), m_NewSetup.HTTPBindIP);
SetCurrent(Get(1));
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));
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));
Add(new cMenuEditIntItem (tr("VDR-to-VDR Server Port"), &m_NewSetup.VTPServerPort, 0, 65535));
Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.VTPBindIP));
AddCategory (tr("HTTP Server"));
Add(new cMenuEditBoolItem(tr("Start HTTP Server"), &m_NewSetup.StartHTTPServer));
Add(new cMenuEditIntItem (tr("HTTP Server Port"), &m_NewSetup.HTTPServerPort, 0, 65535));
Add(new cMenuEditStraItem(tr("HTTP Streamtype"), &m_NewSetup.HTTPStreamType, st_Count - 1, StreamTypes));
Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.HTTPBindIP));
AddCategory (tr("Multicast Streaming Server"));
Add(new cMenuEditBoolItem(tr("Start IGMP Server"), &m_NewSetup.StartIGMPServer));
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(current));
Display();
}
void cStreamdevServerMenuSetupPage::AddCategory(const char *Title) {
cString str = cString::sprintf("--- %s -------------------------------------------------"
"---------------", Title );
cOsdItem *item = new cOsdItem(*str);
item->SetSelectable(false);
Add(item);
}
void cStreamdevServerMenuSetupPage::Store(void) {
bool restart = false;
if (m_NewSetup.StartVTPServer != StreamdevServerSetup.StartVTPServer
@@ -70,7 +119,10 @@ void cStreamdevServerMenuSetupPage::Store(void) {
|| strcmp(m_NewSetup.VTPBindIP, StreamdevServerSetup.VTPBindIP) != 0
|| m_NewSetup.StartHTTPServer != StreamdevServerSetup.StartHTTPServer
|| m_NewSetup.HTTPServerPort != StreamdevServerSetup.HTTPServerPort
|| strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0) {
|| strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0
|| m_NewSetup.StartIGMPServer != StreamdevServerSetup.StartIGMPServer
|| m_NewSetup.IGMPClientPort != StreamdevServerSetup.IGMPClientPort
|| strcmp(m_NewSetup.IGMPBindIP, StreamdevServerSetup.IGMPBindIP) != 0) {
restart = true;
cStreamdevServer::Destruct();
}
@@ -83,6 +135,10 @@ void cStreamdevServerMenuSetupPage::Store(void) {
SetupStore("HTTPServerPort", m_NewSetup.HTTPServerPort);
SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType);
SetupStore("HTTPBindIP", m_NewSetup.HTTPBindIP);
SetupStore("StartIGMPServer", m_NewSetup.StartIGMPServer);
SetupStore("IGMPClientPort", m_NewSetup.IGMPClientPort);
SetupStore("IGMPStreamType", m_NewSetup.IGMPStreamType);
SetupStore("IGMPBindIP", m_NewSetup.IGMPBindIP);
SetupStore("SuspendMode", m_NewSetup.SuspendMode);
SetupStore("AllowSuspend", m_NewSetup.AllowSuspend);
@@ -92,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;
}

View File

@@ -1,5 +1,5 @@
/*
* $Id: setup.h,v 1.1 2004/12/30 22:44:21 lordjaxom Exp $
* $Id: setup.h,v 1.1.1.1.2.3 2010/07/19 13:50:14 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_SETUPSERVER_H
@@ -20,18 +20,27 @@ struct cStreamdevServerSetup {
int HTTPServerPort;
int HTTPStreamType;
char HTTPBindIP[20];
int StartIGMPServer;
int IGMPClientPort;
int IGMPStreamType;
char IGMPBindIP[20];
int SuspendMode;
int AllowSuspend;
};
extern cStreamdevServerSetup StreamdevServerSetup;
class cStreamdevServerMenuSetupPage: public cStreamdevMenuSetupPage {
class cStreamdevServerMenuSetupPage: public cMenuSetupPage {
private:
static const char* StreamTypes[];
static const char* SuspendModes[];
cStreamdevServerSetup m_NewSetup;
void AddCategory(const char *Title);
void Set();
protected:
virtual void Store(void);
virtual eOSState ProcessKey(eKeys Key);
public:
cStreamdevServerMenuSetupPage(void);

View File

@@ -1,5 +1,5 @@
/*
* $Id: streamer.c,v 1.16 2007/09/21 11:45:53 schmirl Exp $
* $Id: streamer.c,v 1.16.2.4 2010/07/19 13:50:14 schmirl Exp $
*/
#include <vdr/ringbuffer.h>
@@ -14,22 +14,28 @@
#include "tools/select.h"
#include "common.h"
// --- cStreamdevBuffer -------------------------------------------------------
cStreamdevBuffer::cStreamdevBuffer(int Size, int Margin, bool Statistics, const char *Description):
cRingBufferLinear(Size, Margin, Statistics, Description)
{
}
// --- cStreamdevWriter -------------------------------------------------------
cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket,
cStreamdevStreamer *Streamer):
cThread("streamdev-writer"),
m_Streamer(Streamer),
m_Socket(Socket),
m_Active(false)
m_Socket(Socket)
{
}
cStreamdevWriter::~cStreamdevWriter()
{
Dprintf("destructing writer\n");
m_Active = false;
Cancel(3);
if (Running())
Cancel(3);
}
void cStreamdevWriter::Action(void)
@@ -39,11 +45,10 @@ void cStreamdevWriter::Action(void)
int max = 0;
uchar *block = NULL;
int count, offset = 0;
m_Active = true;
sel.Clear();
sel.Add(*m_Socket, true);
while (m_Active) {
while (Running()) {
if (block == NULL) {
block = m_Streamer->Get(count);
offset = 0;
@@ -57,39 +62,54 @@ void cStreamdevWriter::Action(void)
if (sel.CanWrite(*m_Socket)) {
int written;
if ((written = m_Socket->Write(block + offset, count)) == -1) {
esyslog("ERROR: streamdev-server: couldn't send data: %m");
int pkgsize = count;
// SOCK_DGRAM indicates multicast
if (m_Socket->Type() == SOCK_DGRAM) {
// don't fragment multicast packets
// max. payload on standard local ethernet is 1416 to 1456 bytes
// and some STBs expect complete TS packets
// so let's always limit to 7 * TS_SIZE = 1316
if (pkgsize > 7 * TS_SIZE)
pkgsize = 7 * TS_SIZE;
else
pkgsize -= pkgsize % TS_SIZE;
}
if ((written = m_Socket->Write(block + offset, pkgsize)) == -1) {
esyslog("ERROR: streamdev-server: couldn't send %d bytes: %m", pkgsize);
break;
}
// statistics
if (count > max)
max = count;
offset += written;
count -= written;
if (count == 0) {
// less than one TS packet left:
// delete what we've written so far and get next chunk
if (count < TS_SIZE) {
m_Streamer->Del(offset);
block = NULL;
}
}
}
}
m_Active = false;
Dprintf("Max. Transmit Blocksize was: %d\n", max);
}
// --- cStreamdevStreamer -----------------------------------------------------
cStreamdevStreamer::cStreamdevStreamer(const char *Name):
cStreamdevStreamer::cStreamdevStreamer(const char *Name, const cServerConnection *Connection):
cThread(Name),
m_Active(false),
m_Running(false),
m_Connection(Connection),
m_Writer(NULL),
m_RingBuffer(new cRingBufferLinear(STREAMERBUFSIZE, TS_SIZE * 2,
m_RingBuffer(new cStreamdevBuffer(STREAMERBUFSIZE, TS_SIZE * 2,
true, "streamdev-streamer")),
m_SendBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2))
m_SendBuffer(new cStreamdevBuffer(WRITERBUFSIZE, TS_SIZE * 2))
{
m_RingBuffer->SetTimeouts(0, 100);
m_SendBuffer->SetTimeouts(0, 100);
m_SendBuffer->SetTimeouts(100, 100);
}
cStreamdevStreamer::~cStreamdevStreamer()
@@ -103,13 +123,12 @@ void cStreamdevStreamer::Start(cTBSocket *Socket)
{
Dprintf("start streamer\n");
m_Writer = new cStreamdevWriter(Socket, this);
m_Running = true;
Attach();
}
void cStreamdevStreamer::Activate(bool On)
{
if (On && !m_Active) {
if (On && !Active()) {
Dprintf("activate streamer\n");
m_Writer->Start();
cThread::Start();
@@ -118,22 +137,19 @@ void cStreamdevStreamer::Activate(bool On)
void cStreamdevStreamer::Stop(void)
{
if (m_Active) {
if (Running()) {
Dprintf("stopping streamer\n");
m_Active = false;
Cancel(3);
}
if (m_Running) {
if (m_Writer) {
Detach();
m_Running = false;
DELETENULL(m_Writer);
}
}
void cStreamdevStreamer::Action(void)
{
m_Active = true;
while (m_Active) {
while (Running()) {
int got;
uchar *block = m_RingBuffer->Get(got);
@@ -141,8 +157,6 @@ void cStreamdevStreamer::Action(void)
int count = Put(block, got);
if (count)
m_RingBuffer->Del(count);
else
cCondWait::SleepMs(100);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* $Id: streamer.h,v 1.8 2007/04/02 10:32:34 schmirl Exp $
* $Id: streamer.h,v 1.8.2.4 2010/07/19 13:50:14 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_STREAMER_H
@@ -11,9 +11,40 @@
class cTBSocket;
class cStreamdevStreamer;
class cServerConnection;
#define STREAMERBUFSIZE MEGABYTE(4)
#define WRITERBUFSIZE KILOBYTE(256)
#ifndef TS_SIZE
#define TS_SIZE 188
#endif
#define STREAMERBUFSIZE (20000 * TS_SIZE)
#define WRITERBUFSIZE (5000 * TS_SIZE)
// --- cStreamdevBuffer -------------------------------------------------------
class cStreamdevBuffer: public cRingBufferLinear {
public:
// make public
void WaitForPut(void) { cRingBuffer::WaitForPut(); }
// Always write complete TS packets
// (assumes Count is a multiple of TS_SIZE)
int PutTS(const uchar *Data, int Count);
cStreamdevBuffer(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL);
};
inline int cStreamdevBuffer::PutTS(const uchar *Data, int Count)
{
int free = Free();
if (free < Count)
Count = free;
Count -= Count % TS_SIZE;
if (Count)
Count = Put(Data, Count);
else
WaitForPut();
return Count;
}
// --- cStreamdevWriter -------------------------------------------------------
@@ -21,7 +52,6 @@ class cStreamdevWriter: public cThread {
private:
cStreamdevStreamer *m_Streamer;
cTBSocket *m_Socket;
bool m_Active;
protected:
virtual void Action(void);
@@ -29,38 +59,37 @@ protected:
public:
cStreamdevWriter(cTBSocket *Socket, cStreamdevStreamer *Streamer);
virtual ~cStreamdevWriter();
bool IsActive(void) const { return m_Active; }
};
// --- cStreamdevStreamer -----------------------------------------------------
class cStreamdevStreamer: public cThread {
private:
bool m_Active;
bool m_Running;
const cServerConnection *m_Connection;
cStreamdevWriter *m_Writer;
cRingBufferLinear *m_RingBuffer;
cRingBufferLinear *m_SendBuffer;
cStreamdevBuffer *m_RingBuffer;
cStreamdevBuffer *m_SendBuffer;
protected:
virtual void Action(void);
bool IsRunning(void) const { return m_Running; }
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) const;
bool Abort(void);
void Activate(bool On);
int Receive(uchar *Data, int Length) { return m_RingBuffer->Put(Data, Length); }
int Receive(uchar *Data, int Length) { return m_RingBuffer->PutTS(Data, Length); }
void ReportOverflow(int Bytes) { m_RingBuffer->ReportOverflow(Bytes); }
virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->Put(Data, Count); }
virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->PutTS(Data, Count); }
virtual uchar *Get(int &Count) { return m_SendBuffer->Get(Count); }
virtual void Del(int Count) { m_SendBuffer->Del(Count); }
@@ -68,9 +97,9 @@ public:
virtual void Attach(void) {}
};
inline bool cStreamdevStreamer::Abort(void) const
inline bool cStreamdevStreamer::Abort(void)
{
return m_Active && !m_Writer->IsActive();
return Active() && !m_Writer->Active();
}
#endif // VDR_STREAMDEV_STREAMER_H

View File

@@ -1,5 +1,5 @@
/*
* $Id: suspend.c,v 1.2 2008/04/07 14:27:31 schmirl Exp $
* $Id: suspend.c,v 1.2.2.1 2008/10/22 11:59:37 schmirl Exp $
*/
#include "server/suspend.h"
@@ -12,6 +12,7 @@ cSuspendLive::cSuspendLive(void)
}
cSuspendLive::~cSuspendLive() {
Stop();
Detach();
}
@@ -24,17 +25,14 @@ void cSuspendLive::Activate(bool On) {
}
void cSuspendLive::Stop(void) {
if (m_Active) {
m_Active = false;
if (Running())
Cancel(3);
}
}
void cSuspendLive::Action(void) {
m_Active = true;
while (m_Active) {
while (Running()) {
DeviceStillPicture(suspend_mpg, sizeof(suspend_mpg));
usleep(100000);
cCondWait::SleepMs(100);
}
}
@@ -51,7 +49,7 @@ cSuspendCtl::~cSuspendCtl() {
}
eOSState cSuspendCtl::ProcessKey(eKeys Key) {
if (!m_Suspend->IsActive() || Key == kBack) {
if (!m_Suspend->Active() || Key == kBack) {
DELETENULL(m_Suspend);
return osEnd;
}

View File

@@ -1,5 +1,5 @@
/*
* $Id: suspend.h,v 1.1 2004/12/30 22:44:26 lordjaxom Exp $
* $Id: suspend.h,v 1.1.1.1.2.1 2008/10/22 11:59:37 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_SUSPEND_H
@@ -7,10 +7,7 @@
#include <vdr/player.h>
class cSuspendLive: public cPlayer, cThread {
private:
bool m_Active;
class cSuspendLive: public cPlayer, public cThread {
protected:
virtual void Activate(bool On);
virtual void Action(void);
@@ -20,8 +17,6 @@ protected:
public:
cSuspendLive(void);
virtual ~cSuspendLive();
bool IsActive(void) const { return m_Active; }
};
class cSuspendCtl: public cControl {

View File

@@ -3,7 +3,7 @@
*
* See the README file for copyright information and how to reach the author.
*
* $Id: streamdev-client.c,v 1.5 2008/04/07 14:50:32 schmirl Exp $
* $Id: streamdev-client.c,v 1.5.2.2 2010/08/18 10:26:18 schmirl Exp $
*/
#include "streamdev-client.h"
@@ -36,11 +36,6 @@ bool cPluginStreamdevClient::Start(void) {
return true;
}
void cPluginStreamdevClient::Housekeeping(void) {
if (StreamdevClientSetup.StartClient && StreamdevClientSetup.SyncEPG)
ClientSocket.SynchronizeEPG();
}
const char *cPluginStreamdevClient::MainMenuEntry(void) {
return StreamdevClientSetup.StartClient && !StreamdevClientSetup.HideMenuEntry ? tr("Suspend Server") : NULL;
}
@@ -61,4 +56,8 @@ bool cPluginStreamdevClient::SetupParse(const char *Name, const char *Value) {
return StreamdevClientSetup.SetupParse(Name, Value);
}
void cPluginStreamdevClient::MainThreadHook(void) {
cStreamdevDevice::UpdatePriority();
}
VDRPLUGINCREATOR(cPluginStreamdevClient); // Don't touch this!

View File

@@ -1,5 +1,5 @@
/*
* $Id: streamdev-client.h,v 1.1 2004/12/30 22:43:59 lordjaxom Exp $
* $Id: streamdev-client.h,v 1.1.1.1.2.2 2010/08/18 10:26:18 schmirl Exp $
*/
#ifndef VDR_STREAMDEVCLIENT_H
@@ -19,11 +19,11 @@ public:
virtual const char *Version(void) { return VERSION; }
virtual const char *Description(void);
virtual bool Start(void);
virtual void Housekeeping(void);
virtual const char *MainMenuEntry(void);
virtual cOsdObject *MainMenuAction(void);
virtual cMenuSetupPage *SetupMenu(void);
virtual bool SetupParse(const char *Name, const char *Value);
virtual void MainThreadHook(void);
};
#endif // VDR_STREAMDEVCLIENT_H

View File

@@ -3,15 +3,15 @@
*
* See the README file for copyright information and how to reach the author.
*
* $Id: streamdev-server.c,v 1.7 2008/04/07 14:27:27 schmirl Exp $
* $Id: streamdev-server.c,v 1.7.2.3 2009/06/29 06:25:27 schmirl Exp $
*/
#include <getopt.h>
#include <vdr/tools.h>
#include "streamdev-server.h"
#include "server/setup.h"
#include "server/server.h"
#include "server/suspend.h"
#include "remux/extern.h"
#include "i18n.h"
#if VDRVERSNUM < 10400
@@ -26,6 +26,8 @@ cPluginStreamdevServer::cPluginStreamdevServer(void)
cPluginStreamdevServer::~cPluginStreamdevServer()
{
free(opt_auth);
free(opt_remux);
}
const char *cPluginStreamdevServer::Description(void)
@@ -36,22 +38,39 @@ const char *cPluginStreamdevServer::Description(void)
const char *cPluginStreamdevServer::CommandLineHelp(void)
{
// return a string that describes all known command line options.
return " -r <CMD>, --remux=<CMD> Define an external command for remuxing.\n";
return
" -a <LOGIN:PASSWORD>, --auth=<LOGIN:PASSWORD> Credentials for HTTP authentication.\n"
" -r <CMD>, --remux=<CMD> Define an external command for remuxing.\n"
;
}
bool cPluginStreamdevServer::ProcessArgs(int argc, char *argv[])
{
// implement command line argument processing here if applicable.
static const struct option long_options[] = {
{ "auth", required_argument, NULL, 'a' },
{ "remux", required_argument, NULL, 'r' },
{ NULL, 0, NULL, 0 }
};
int c;
while((c = getopt_long(argc, argv, "r:", long_options, NULL)) != -1) {
while((c = getopt_long(argc, argv, "a:r:", long_options, NULL)) != -1) {
switch (c) {
case 'a':
{
if (opt_auth)
free(opt_auth);
int l = strlen(optarg);
cBase64Encoder Base64((uchar*) optarg, l, l * 4 / 3 + 3);
const char *s = Base64.NextLine();
if (s)
opt_auth = strdup(s);
}
break;
case 'r':
g_ExternRemux = optarg;
if (opt_remux)
free(opt_remux);
opt_remux = strdup(optarg);
break;
default:
return false;
@@ -77,6 +96,8 @@ bool cPluginStreamdevServer::Start(void)
}
return false;
}
if (!opt_remux)
opt_remux = strdup(DEFAULT_EXTERNREMUX);
cStreamdevServer::Initialize();

View File

@@ -1,48 +1,315 @@
#!/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
# HEIGHT scale video to height
# FPS output frames per second
# 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
### video part
# Default video codec (e.g. lavc/x264/copy)
MENCODER_VC=lavc
# Default video options if lavc is used (-ovc lavc -lavcopts ...)
MENCODER_LAVC_VOPTS=vcodec=mpeg4
# Default video options if x264 is used (-ovc x264 -x264encopts ...)
MENCODER_X264_VOPTS=threads=auto
### audio part
# Default audio codec (e.g. lavc/mp3lame/faac/copy)
MENCODER_AC=mp3lame
# Default audio options if lavc is used (-oac lavc -lavcopts ...)
MENCODER_LAVC_AOPTS=acodec=mp2
# Default audio options if mp3lame is used (-oac mp3lame -lameopts ...)
MENCODER_LAME_AOPTS=
# Default audio options if faac is used (-oac faac -faacopts ...)
MENCODER_FAAC_AOPTS=
###
### 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
# aspect ratio used for scaling if only one of HEIGHT/WIDTH given (16/9 or 4/3)
OGG_ASPECT='4 / 3'
###
### OGG CONFIG END
##########################################################################
function hasOpt { echo "$1" | grep -q "\b${2}\b"; }
# $1: concatenation of already set option=value pairs
# $2-$n: option=value pairs to be echod if the option is not present in $1
function addOpts
{
local opts="$1"
shift
while [ $# -gt 0 ]; do
hasOpt "$opts" ${1%%=*}= || echo $1
shift
done
}
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}
FPS=${REMUX_PARAM_FPS:-$FPS}
# if only one of HEIGHT/WIDTH given:
# have mencoder calculate other value depending on actual aspect ratio
if [ "$HEIGHT" -a -z "$WIDTH" ]; then
WIDTH=-3
elif [ "$WIDTH" -a -z "$HEIGHT" ]; then
HEIGHT=-3
fi
case "$VC" in
lavc)
LAVCOPTS=(
${VOPTS}
$(IFS=$IFS:; addOpts "$VOPTS" $MENCODER_LAVC_VOPTS)
${VBR:+vbitrate=$VBR}
)
[ ${#LAVCOPTS[*]} -gt 0 ] && VOPTS=$(IFS=:; echo -lavcopts "${LAVCOPTS[*]}")
;;
x264)
isNumeric "$HEIGHT" && [ $HEIGHT -lt 0 -a $HEIGHT -gt -8 ] && ((HEIGHT-=8))
isNumeric "$WIDTH" && [ $WIDTH -lt 0 -a $WIDTH -gt -8 ] && ((WIDTH-=8))
X264OPTS=(
${VOPTS}
$(IFS=$IFS:; addOpts "$VOPTS" $MENCODER_X264_VOPTS)
${VBR:+bitrate=$VBR}
)
[ ${#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}
$(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_LAVC_AOPTS)
${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')
$(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_LAME_AOPTS)
${ABR:+preset=$ABR}
)
[ ${#LAMEOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -lameopts "${LAMEOPTS[*]}")
;;
faac)
FAACOPTS=(
${AOPTS}
$(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_FAAC_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=$WIDTH:$HEIGHT -zoom} \
${FPS:+-ofps $FPS} \
-o "$FIFO" -- - >&2
$MENCODER \
-ovc $VC $VOPTS \
-oac $AC $AOPTS \
${WIDTH:+-vf scale=$WIDTH:$HEIGHT -zoom} \
${FPS:+-ofps $FPS} \
-o "$FIFO" -- - 0<&3 >/dev/null &
}
function remux_ogg
{
VOPTS=${REMUX_PARAM_VOPTS//[:=]/ }
AOPTS=${REMUX_PARAM_AOPTS//[:=]/ }
# if only one of HEIGHT/WIDTH given:
# calculate other value depending on configured aspect ratio
# trim to multiple of 8
if [ "$HEIGHT" -a -z "$WIDTH" ]; then
WIDTH=$((HEIGHT * $OGG_ASPECT / 8 * 8))
elif [ "$WIDTH" -a -z "$HEIGHT" ]; then
HEIGHT=$(($WIDTH * $( echo $OGG_ASPECT | sed 's#^\([0-9]\+\) */ *\([0-9]\+\)$#\2 / \1#') / 8 * 8))
fi
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 $HEIGHT} \
--title "VDR Streamdev: ${REMUX_CHANNEL_NAME}" \
--output "$FIFO" -- - 0<&3 >&2
$OGG --format ts \
${OGGOPTS[*]} \
${WIDTH:+--width $WIDTH --height $HEIGHT} \
--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;;
WLAN54|wlan54) 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}
HEIGHT=${REMUX_PARAM_HEIGHT:-$HEIGHT}
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

View File

@@ -10,4 +10,5 @@
127.0.0.1 # always accept localhost
#192.168.100.0/24 # any host on the local net
#204.152.189.113 # a specific host
#0.0.0.0/0 # any host on any net (USE THIS WITH CARE!)
#239.255.0.0/16 # uncomment for IGMP multicast streaming
#0.0.0.0/0 # any host on any net (DON'T DO THAT! USE AUTHENTICATION)

View File

@@ -1,5 +1,6 @@
#include "tools/socket.h"
#include <vdr/tools.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
@@ -15,10 +16,11 @@
// actual DSCP value used
#define STREAMDEV_DSCP DSCP_AF41
cTBSocket::cTBSocket(int Type) {
cTBSocket::cTBSocket(int Type, int Protocol) {
memset(&m_LocalAddr, 0, sizeof(m_LocalAddr));
memset(&m_RemoteAddr, 0, sizeof(m_RemoteAddr));
m_Type = Type;
m_Protocol = Protocol;
}
cTBSocket::~cTBSocket() {
@@ -31,7 +33,7 @@ bool cTBSocket::Connect(const std::string &Host, unsigned int Port) {
if (IsOpen()) Close();
if ((socket = ::socket(PF_INET, m_Type, IPPROTO_IP)) == -1)
if ((socket = ::socket(PF_INET, m_Type, m_Protocol)) == -1)
return false;
m_LocalAddr.sin_family = AF_INET;
@@ -52,10 +54,12 @@ bool cTBSocket::Connect(const std::string &Host, unsigned int Port) {
return false;
}
len = sizeof(struct sockaddr_in);
if (::getpeername(socket, (struct sockaddr*)&m_RemoteAddr, &len) == -1) {
::close(socket);
return false;
if (m_Type == SOCK_STREAM) {
len = sizeof(struct sockaddr_in);
if (::getpeername(socket, (struct sockaddr*)&m_RemoteAddr, &len) == -1) {
::close(socket);
return false;
}
}
len = sizeof(struct sockaddr_in);
@@ -64,7 +68,11 @@ bool cTBSocket::Connect(const std::string &Host, unsigned int Port) {
return false;
}
return cTBSource::Open(socket);
if (!cTBSource::Open(socket)) {
::close(socket);
return false;
}
return true;
}
bool cTBSocket::Listen(const std::string &Ip, unsigned int Port, int BackLog) {
@@ -74,7 +82,7 @@ bool cTBSocket::Listen(const std::string &Ip, unsigned int Port, int BackLog) {
if (IsOpen()) Close();
if ((socket = ::socket(PF_INET, m_Type, IPPROTO_IP)) == -1)
if ((socket = ::socket(PF_INET, m_Type, m_Protocol)) == -1)
return false;
val = 1;
@@ -116,6 +124,10 @@ bool cTBSocket::Accept(const cTBSocket &Listener) {
if (::getsockname(socket, (struct sockaddr*)&m_LocalAddr, &addrlen) == -1)
return false;
int sol=1;
// Ignore possible errors here, proceed as usual
::setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &sol, sizeof(sol));
if (!cTBSource::Open(socket))
return false;

View File

@@ -18,9 +18,10 @@ private:
struct sockaddr_in m_RemoteAddr;
int m_Type;
int m_Protocol;
public:
cTBSocket(int Type = SOCK_STREAM);
cTBSocket(int Type = SOCK_STREAM, int Protocol = 0);
virtual ~cTBSocket();
/* See cTBSource::SysRead()
@@ -97,15 +98,22 @@ public:
};
inline ssize_t cTBSocket::SysRead(void *Buffer, size_t Length) const {
if (m_Type == SOCK_DGRAM) {
if (m_Type == SOCK_STREAM)
return ::recv(*this, Buffer, Length, 0);
else {
socklen_t len = sizeof(m_RemoteAddr);
return ::recvfrom(*this, Buffer, Length, 0, (sockaddr*)&m_RemoteAddr, &len);
} else
return ::recv(*this, Buffer, Length, 0);
}
}
inline ssize_t cTBSocket::SysWrite(const void *Buffer, size_t Length) const {
return ::send(*this, Buffer, Length, 0);
if (m_Type == SOCK_STREAM)
return ::send(*this, Buffer, Length, 0);
else {
socklen_t len = sizeof(m_RemoteAddr);
return ::sendto(*this, Buffer, Length, 0, (sockaddr*)&m_RemoteAddr, len);
}
}
#endif // TOOLBOX_SOCKET_H