Snapshot 2009-07-01

This commit is contained in:
Frank Schmirler 2010-12-02 09:44:53 +01:00
parent 7254a67528
commit 008ea7f151
28 changed files with 3462 additions and 329 deletions

View File

@ -1,6 +1,10 @@
Special thanks go to the following persons (if you think your name is missing Special thanks go to the following persons (if you think your name is missing
here, please send an email to vdrdev@schmirler.de): 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, Sascha Volkenandt, the original author,
for this great plugin for this great plugin
@ -30,6 +34,7 @@ Rolf Ahrenberg
for replacing private members by cThread::Running()/Active() for replacing private members by cThread::Running()/Active()
for improving externremux script termination for improving externremux script termination
for fixing PAT repacker version field for fixing PAT repacker version field
for correcting LIMIKUUTIO patch detection
Rantanen Teemu Rantanen Teemu
for providing vdr-incompletesections.diff for providing vdr-incompletesections.diff
@ -74,6 +79,7 @@ alexw
Olli Lammi Olli Lammi
for fixing a busy wait when client isn't accepting data fast enough 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 Joerg Pulz
for his FreeBSD compatibility patch for his FreeBSD compatibility patch
@ -111,3 +117,6 @@ Joachim K
Artem Makhutov Artem Makhutov
for suggesting and heavy testing IGMP based multicast streaming for suggesting and heavy testing IGMP based multicast streaming
Alwin Esch
for adding XBMC support by extending VTP capabilities

17
HISTORY
View File

@ -1,6 +1,23 @@
VDR Plugin 'streamdev' Revision History VDR Plugin 'streamdev' Revision History
--------------------------------------- ---------------------------------------
- added XBMC support by extending VTP capabilities (thanks to Alwin Esch)
- now there's a common baseclass for all remuxers, make use of it
- added cDevice::NumProvidedSystems() which was introduced in VDR 1.7.0
- 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
- re-enabled PES vor VDR 1.7.3+. Streamdev now uses 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 missing call to StopSectionHandler which could cause crashes when - added missing call to StopSectionHandler which could cause crashes when
shutting down VDR shutting down VDR
- added IGMP based multicast streaming - added IGMP based multicast streaming

View File

@ -1,7 +1,7 @@
# #
# Makefile for a Video Disk Recorder plugin # Makefile for a Video Disk Recorder plugin
# #
# $Id: Makefile,v 1.17 2009/02/13 10:39:20 schmirl Exp $ # $Id: Makefile,v 1.19 2009/07/01 10:46:15 schmirl Exp $
# The official name of this plugin. # The official name of this plugin.
# This name will be used in the '-P...' option of VDR to load the plugin. # This name will be used in the '-P...' option of VDR to load the plugin.
@ -61,8 +61,8 @@ SERVEROBJS = $(PLUGIN)-server.o \
server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \ server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \
server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \ server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \
server/streamer.o server/livestreamer.o server/livefilter.o \ server/streamer.o server/livestreamer.o server/livefilter.o \
server/suspend.o server/setup.o server/menuHTTP.o \ server/suspend.o server/setup.o server/menuHTTP.o server/recplayer.o \
remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o
ifdef DEBUG ifdef DEBUG
DEFINES += -DDEBUG DEFINES += -DDEBUG

View File

@ -1,5 +1,5 @@
/* /*
* $Id: device.h,v 1.8 2008/10/02 07:14:47 schmirl Exp $ * $Id: device.h,v 1.9 2009/06/23 10:26:54 schmirl Exp $
*/ */
#ifndef VDR_STREAMDEV_DEVICE_H #ifndef VDR_STREAMDEV_DEVICE_H
@ -54,6 +54,9 @@ public:
virtual bool ProvidesTransponder(const cChannel *Channel) const; virtual bool ProvidesTransponder(const cChannel *Channel) const;
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1,
bool *NeedsDetachReceivers = NULL) const; bool *NeedsDetachReceivers = NULL) const;
#if APIVERSNUM >= 10700
virtual int NumProvidedSystems(void) const { return 1; }
#endif
virtual bool IsTunedToTransponder(const cChannel *Channel); virtual bool IsTunedToTransponder(const cChannel *Channel);
static bool Init(void); static bool Init(void);

View File

@ -1,5 +1,5 @@
/* /*
* $Id: common.c,v 1.9 2009/01/16 11:35:43 schmirl Exp $ * $Id: common.c,v 1.10 2009/06/19 06:32:38 schmirl Exp $
*/ */
#include <vdr/channels.h> #include <vdr/channels.h>
@ -10,13 +10,11 @@
using namespace std; using namespace std;
const char *VERSION = "0.5.0-pre-20090611"; const char *VERSION = "0.5.0-pre-20090701";
const char *StreamTypes[st_Count] = { const char *StreamTypes[st_Count] = {
"TS", "TS",
#if APIVERSNUM < 10703
"PES", "PES",
#endif
"PS", "PS",
"ES", "ES",
"Extern", "Extern",

View File

@ -1,5 +1,5 @@
/* /*
* $Id: common.h,v 1.12 2009/01/16 11:35:43 schmirl Exp $ * $Id: common.h,v 1.14 2009/07/01 10:46:16 schmirl Exp $
*/ */
#ifndef VDR_STREAMDEV_COMMON_H #ifndef VDR_STREAMDEV_COMMON_H
@ -51,9 +51,7 @@ const cChannel *ChannelFromString(const char *String, int *Apid = NULL);
enum eStreamType { enum eStreamType {
stTS, stTS,
#if APIVERSNUM < 10703
stPES, stPES,
#endif
stPS, stPS,
stES, stES,
stExtern, stExtern,
@ -74,6 +72,7 @@ enum eSocketId {
siLive, siLive,
siReplay, siReplay,
siLiveFilter, siLiveFilter,
siDataRespond,
si_Count si_Count
}; };

View File

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

View File

@ -7,6 +7,8 @@
#include <signal.h> #include <signal.h>
#include <unistd.h> #include <unistd.h>
namespace Streamdev {
class cTSExt: public cThread { class cTSExt: public cThread {
private: private:
cRingBufferLinear *m_ResultBuffer; cRingBufferLinear *m_ResultBuffer;
@ -24,6 +26,9 @@ public:
void Put(const uchar *Data, int Count); void Put(const uchar *Data, int Count);
}; };
} // namespace Streamdev
using namespace Streamdev;
cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter): cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter):
m_ResultBuffer(ResultBuffer), m_ResultBuffer(ResultBuffer),
m_Active(false), m_Active(false),

View File

@ -5,6 +5,8 @@
#include <vdr/ringbuffer.h> #include <vdr/ringbuffer.h>
#include <string> #include <string>
namespace Streamdev {
class cTSExt; class cTSExt;
class cExternRemux: public cTSRemux { class cExternRemux: public cTSRemux {
@ -21,4 +23,6 @@ public:
void Del(int Count) { m_ResultBuffer->Del(Count); } void Del(int Count) { m_ResultBuffer->Del(Count); }
}; };
} // namespace Streamdev
#endif // VDR_STREAMDEV_EXTERNREMUX_H #endif // VDR_STREAMDEV_EXTERNREMUX_H

View File

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

View File

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

2017
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.3 2009/06/30 06:04:33 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).
~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/channels.h>
#include <vdr/device.h> #include <vdr/device.h>
namespace Streamdev {
class cTS2PS { class cTS2PS {
friend void PutPES(uint8_t *Buffer, int Size, void *Data); 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); 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) cTS2PS::cTS2PS(cRingBufferLinear *ResultBuffer, int Pid, uint8_t AudioCid)
{ {
m_ResultBuffer = ResultBuffer; 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): cTS2PSRemux::cTS2PSRemux(int VPid, const int *APids, const int *DPids, const int *SPids):
m_NumTracks(0), m_NumTracks(0),
m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, IPACKS)), m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)),
m_ResultSkipped(0), m_ResultSkipped(0),
m_Skipped(0), m_Skipped(0),
m_Synced(false), m_Synced(false),
m_IsRadio(VPid == 0 || VPid == 1 || VPid == 0x1FFF) m_IsRadio(VPid == 0 || VPid == 1 || VPid == 0x1FFF)
{ {
m_ResultBuffer->SetTimeouts(0, 100); m_ResultBuffer->SetTimeouts(100, 100);
if (VPid) if (VPid)
m_Remux[m_NumTracks++] = new cTS2PS(m_ResultBuffer, VPid); m_Remux[m_NumTracks++] = new cTS2PS(m_ResultBuffer, VPid);
@ -124,8 +129,10 @@ int cTS2PSRemux::Put(const uchar *Data, int Count)
break; break;
if (Data[i] != TS_SYNC_BYTE) if (Data[i] != TS_SYNC_BYTE)
break; 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 break; // A cTS2PS might write one full packet and also a small rest
}
int pid = GetPid(Data + i + 1); int pid = GetPid(Data + i + 1);
if (Data[i + 3] & 0x10) { // got payload if (Data[i + 3] & 0x10) { // got payload
for (int t = 0; t < m_NumTracks; t++) { for (int t = 0; t < m_NumTracks; t++) {

View File

@ -2,20 +2,21 @@
#define VDR_STREAMDEV_TS2PESREMUX_H #define VDR_STREAMDEV_TS2PESREMUX_H
#include "remux/tsremux.h" #include "remux/tsremux.h"
#include <vdr/remux.h> #include "server/streamer.h"
#include <vdr/ringbuffer.h>
#ifndef MAXTRACKS #ifndef MAXTRACKS
#define MAXTRACKS 64 #define MAXTRACKS 64
#endif #endif
namespace Streamdev {
class cTS2PS; class cTS2PS;
class cTS2PSRemux: public cTSRemux { class cTS2PSRemux: public cTSRemux {
private: private:
int m_NumTracks; int m_NumTracks;
cTS2PS *m_Remux[MAXTRACKS]; cTS2PS *m_Remux[MAXTRACKS];
cRingBufferLinear *m_ResultBuffer; cStreamdevBuffer *m_ResultBuffer;
int m_ResultSkipped; int m_ResultSkipped;
int m_Skipped; int m_Skipped;
bool m_Synced; bool m_Synced;
@ -30,4 +31,6 @@ public:
void Del(int Count) { m_ResultBuffer->Del(Count); } void Del(int Count) { m_ResultBuffer->Del(Count); }
}; };
} // namespace Streamdev
#endif // VDR_STREAMDEV_TS2PESREMUX_H #endif // VDR_STREAMDEV_TS2PESREMUX_H

View File

@ -2,11 +2,15 @@
#define SC_PICTURE 0x00 // "picture header" #define SC_PICTURE 0x00 // "picture header"
#define PID_MASK_HI 0x1F #define PID_MASK_HI 0x1F
#define VIDEO_STREAM_S 0xE0
using namespace Streamdev;
void cTSRemux::SetBrokenLink(uchar *Data, int Length) 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) { int PesPayloadOffset = 0;
for (int i = Data[8] + 9; i < Length - 7; i++) { // +9 to skip video packet header 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] == 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 if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed
Data[i + 7] |= 0x20; Data[i + 7] |= 0x20;
@ -40,16 +44,39 @@ 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. // If the return value is -1 the packet was not completely in the buffer.
int Length = GetPacketLength(Data, Count, Offset); int Length = GetPacketLength(Data, Count, Offset);
if (Length > 0) { if (Length > 0) {
if (Length >= 8) { int PesPayloadOffset = 0;
int i = Offset + 8; // the minimum length of the video packet header if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) {
i += Data[i] + 1; // possible additional header bytes const uchar *p = Data + Offset + PesPayloadOffset + 2;
for (; i < Offset + Length - 5; i++) { const uchar *pLimit = Data + Offset + Length - 3;
if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) { #ifdef TEST_cVideoRepacker
switch (Data[i + 3]) { // cVideoRepacker ensures that a new PES packet is started for a new sequence,
case SC_PICTURE: PictureType = (Data[i + 5] >> 3) & 0x07; // 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; return Length;
} }
p += 4; // continue scanning after 0x01ssxxyy
} }
else
p += 3; // continue scanning after 0x01xxyy
} }
} }
PictureType = NO_PICTURE; PictureType = NO_PICTURE;

View File

@ -4,29 +4,19 @@
#include "libdvbmpeg/transform.h" #include "libdvbmpeg/transform.h"
#include <vdr/remux.h> #include <vdr/remux.h>
#ifndef NO_PICTURE // Picture types:
#define NO_PICTURE 0 #define NO_PICTURE 0
#endif #define I_FRAME 1
#define P_FRAME 2
#define B_FRAME 3
#define RESULTBUFFERSIZE KILOBYTE(256) namespace Streamdev {
class cTSRemux { 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: public:
cTSRemux(bool Sync = true); virtual int Put(const uchar *Data, int Count) = 0;
virtual ~cTSRemux(); virtual uchar *Get(int &Count) = 0;
virtual void Del(int Count) = 0;
virtual uchar *Process(const uchar *Data, int &Count, int &Result);*/
static void SetBrokenLink(uchar *Data, int Length); static void SetBrokenLink(uchar *Data, int Length);
static int GetPid(const uchar *Data); static int GetPid(const uchar *Data);
@ -34,4 +24,6 @@ public:
static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType); static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType);
}; };
} // namespace Streamdev
#endif // VDR_STREAMDEV_TSREMUX_H #endif // VDR_STREAMDEV_TSREMUX_H

View File

@ -1,5 +1,5 @@
/* /*
* $Id: connectionHTTP.c,v 1.16 2009/02/13 07:02:19 schmirl Exp $ * $Id: connectionHTTP.c,v 1.17 2009/06/19 06:32:45 schmirl Exp $
*/ */
#include <ctype.h> #include <ctype.h>
@ -211,10 +211,8 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts)
const char* pType = type.c_str(); const char* pType = type.c_str();
if (strcasecmp(pType, "PS") == 0) { if (strcasecmp(pType, "PS") == 0) {
m_StreamType = stPS; m_StreamType = stPS;
#if APIVERSNUM < 10703
} else if (strcasecmp(pType, "PES") == 0) { } else if (strcasecmp(pType, "PES") == 0) {
m_StreamType = stPES; m_StreamType = stPES;
#endif
} else if (strcasecmp(pType, "TS") == 0) { } else if (strcasecmp(pType, "TS") == 0) {
m_StreamType = stTS; m_StreamType = stTS;
} else if (strcasecmp(pType, "ES") == 0) { } else if (strcasecmp(pType, "ES") == 0) {
@ -266,9 +264,7 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts)
{ {
case stTS: base += "TS/"; break; case stTS: base += "TS/"; break;
case stPS: base += "PS/"; break; case stPS: base += "PS/"; break;
#if APIVERSNUM < 10703
case stPES: base += "PES/"; break; case stPES: base += "PES/"; break;
#endif
case stES: base += "ES/"; break; case stES: base += "ES/"; break;
case stExtern: base += "Extern/"; break; case stExtern: base += "Extern/"; break;
default: break; default: break;

View File

@ -1,5 +1,5 @@
/* /*
* $Id: connectionVTP.c,v 1.19 2009/01/16 11:35:44 schmirl Exp $ * $Id: connectionVTP.c,v 1.21 2009/07/01 10:46:16 schmirl Exp $
*/ */
#include "server/connectionVTP.h" #include "server/connectionVTP.h"
@ -8,6 +8,8 @@
#include "setup.h" #include "setup.h"
#include <vdr/tools.h> #include <vdr/tools.h>
#include <vdr/videodir.h>
#include <vdr/menu.h>
#include <tools/select.h> #include <tools/select.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
@ -28,13 +30,20 @@
563: Recording not available (currently?) 563: Recording not available (currently?)
*/ */
enum eDumpModeStreamdev { dmsdAll, dmsdPresent, dmsdFollowing, dmsdAtTime, dmsdFromToTime };
// --- cLSTEHandler ----------------------------------------------------------- // --- cLSTEHandler -----------------------------------------------------------
class cLSTEHandler class cLSTEHandler
{ {
private: private:
#ifdef USE_PARENTALRATING
enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content,
EndEvent, EndChannel, EndEPG };
#else
enum eStates { Channel, Event, Title, Subtitle, Description, Vps, enum eStates { Channel, Event, Title, Subtitle, Description, Vps,
EndEvent, EndChannel, EndEPG }; EndEvent, EndChannel, EndEPG };
#endif /* PARENTALRATING */
cConnectionVTP *m_Client; cConnectionVTP *m_Client;
cSchedulesLock *m_SchedulesLock; cSchedulesLock *m_SchedulesLock;
const cSchedules *m_Schedules; const cSchedules *m_Schedules;
@ -44,6 +53,7 @@ private:
char *m_Error; char *m_Error;
eStates m_State; eStates m_State;
bool m_Traverse; bool m_Traverse;
time_t m_ToTime;
public: public:
cLSTEHandler(cConnectionVTP *Client, const char *Option); cLSTEHandler(cConnectionVTP *Client, const char *Option);
~cLSTEHandler(); ~cLSTEHandler();
@ -59,10 +69,12 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
m_Errno(0), m_Errno(0),
m_Error(NULL), m_Error(NULL),
m_State(Channel), m_State(Channel),
m_Traverse(false) m_Traverse(false),
m_ToTime(0)
{ {
eDumpMode dumpmode = dmAll; eDumpModeStreamdev dumpmode = dmsdAll;
time_t attime = 0; time_t attime = 0;
time_t fromtime = 0;
if (m_Schedules != NULL && *Option) { if (m_Schedules != NULL && *Option) {
char buf[strlen(Option) + 1]; char buf[strlen(Option) + 1];
@ -70,13 +82,13 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
const char *delim = " \t"; const char *delim = " \t";
char *strtok_next; char *strtok_next;
char *p = strtok_r(buf, delim, &strtok_next); char *p = strtok_r(buf, delim, &strtok_next);
while (p && dumpmode == dmAll) { while (p && dumpmode == dmsdAll) {
if (strcasecmp(p, "NOW") == 0) if (strcasecmp(p, "NOW") == 0)
dumpmode = dmPresent; dumpmode = dmsdPresent;
else if (strcasecmp(p, "NEXT") == 0) else if (strcasecmp(p, "NEXT") == 0)
dumpmode = dmFollowing; dumpmode = dmsdFollowing;
else if (strcasecmp(p, "AT") == 0) { else if (strcasecmp(p, "AT") == 0) {
dumpmode = dmAtTime; dumpmode = dmsdAtTime;
if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
if (isnumber(p)) if (isnumber(p))
attime = strtol(p, NULL, 10); attime = strtol(p, NULL, 10);
@ -90,6 +102,39 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
m_Error = strdup("Missing time"); m_Error = strdup("Missing time");
break; break;
} }
}
else if (strcasecmp(p, "FROM") == 0) {
dumpmode = dmsdFromToTime;
if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
if (isnumber(p))
fromtime = strtol(p, NULL, 10);
else {
m_Errno = 501;
m_Error = strdup("Invalid time");
break;
}
if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
if (strcasecmp(p, "TO") == 0) {
if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
if (isnumber(p))
m_ToTime = strtol(p, NULL, 10);
else {
m_Errno = 501;
m_Error = strdup("Invalid time");
break;
}
} else {
m_Errno = 501;
m_Error = strdup("Missing time");
break;
}
}
}
} else {
m_Errno = 501;
m_Error = strdup("Missing time");
break;
}
} else if (!m_Schedule) { } else if (!m_Schedule) {
cChannel* Channel = NULL; cChannel* Channel = NULL;
if (isnumber(p)) if (isnumber(p))
@ -129,16 +174,29 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
if (m_Schedule != NULL && m_Schedule->Events() != NULL) { if (m_Schedule != NULL && m_Schedule->Events() != NULL) {
switch (dumpmode) { switch (dumpmode) {
case dmAll: m_Event = m_Schedule->Events()->First(); case dmsdAll: m_Event = m_Schedule->Events()->First();
m_Traverse = true; m_Traverse = true;
break; break;
case dmPresent: m_Event = m_Schedule->GetPresentEvent(); case dmsdPresent: m_Event = m_Schedule->GetPresentEvent();
break; break;
case dmFollowing: m_Event = m_Schedule->GetFollowingEvent(); case dmsdFollowing: m_Event = m_Schedule->GetFollowingEvent();
break; break;
case dmAtTime: m_Event = m_Schedule->GetEventAround(attime); case dmsdAtTime: m_Event = m_Schedule->GetEventAround(attime);
break;
case dmsdFromToTime:
if (m_Schedule->Events()->Count() <= 1) {
m_Event = m_Schedule->Events()->First();
break;
}
if (fromtime < m_Schedule->Events()->First()->StartTime()) {
fromtime = m_Schedule->Events()->First()->StartTime();
}
if (m_ToTime > m_Schedule->Events()->Last()->EndTime()) {
m_ToTime = m_Schedule->Events()->Last()->EndTime();
}
m_Event = m_Schedule->GetEventAround(fromtime);
m_Traverse = true;
break; break;
} }
} }
} }
@ -227,7 +285,11 @@ bool cLSTEHandler::Next(bool &Last)
break; break;
case Vps: case Vps:
#ifdef USE_PARENTALRATING
m_State = Content;
#else
m_State = EndEvent; m_State = EndEvent;
#endif /* PARENTALRATING */
if (m_Event->Vps()) if (m_Event->Vps())
#ifdef __FreeBSD__ #ifdef __FreeBSD__
return m_Client->Respond(-215, "V %d", m_Event->Vps()); return m_Client->Respond(-215, "V %d", m_Event->Vps());
@ -238,9 +300,26 @@ bool cLSTEHandler::Next(bool &Last)
return Next(Last); return Next(Last);
break; break;
#ifdef USE_PARENTALRATING
case Content:
m_State = EndEvent;
if (!isempty(m_Event->GetContentsString())) {
char *copy = strdup(m_Event->GetContentsString());
cString cpy(copy, true);
strreplace(copy, '\n', '|');
return m_Client->Respond(-215, "G %i %i %s", m_Event->Contents() & 0xF0, m_Event->Contents() & 0x0F, copy);
} else
return Next(Last);
break;
#endif
case EndEvent: case EndEvent:
if (m_Traverse) if (m_Traverse) {
m_Event = m_Schedule->Events()->Next(m_Event); m_Event = m_Schedule->Events()->Next(m_Event);
if ((m_Event != NULL) && (m_ToTime != 0) && (m_Event->StartTime() > m_ToTime)) {
m_Event = NULL;
}
}
else else
m_Event = NULL; m_Event = NULL;
@ -377,7 +456,7 @@ bool cLSTCHandler::Next(bool &Last)
} }
} }
if (i < Channels.MaxNumber()) if (i < Channels.MaxNumber() + 1)
Last = false; Last = false;
} }
@ -468,6 +547,181 @@ bool cLSTTHandler::Next(bool &Last)
return result; return result;
} }
// --- cLSTRHandler -----------------------------------------------------------
class cLSTRHandler
{
private:
enum eStates { Recording, Event, Title, Subtitle, Description, Components, Vps,
EndRecording };
cConnectionVTP *m_Client;
cRecording *m_Recording;
const cEvent *m_Event;
int m_Index;
int m_Errno;
char *m_Error;
bool m_Traverse;
bool m_Info;
eStates m_State;
int m_CurrentComponent;
public:
cLSTRHandler(cConnectionVTP *Client, const char *Option);
~cLSTRHandler();
bool Next(bool &Last);
};
cLSTRHandler::cLSTRHandler(cConnectionVTP *Client, const char *Option):
m_Client(Client),
m_Recording(NULL),
m_Event(NULL),
m_Index(0),
m_Errno(0),
m_Error(NULL),
m_Traverse(false),
m_Info(false),
m_State(Recording),
m_CurrentComponent(0)
{
if (*Option) {
if (isnumber(Option)) {
m_Recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
#if defined(USE_STREAMDEVEXT) || APIVERSNUM >= 10705
m_Event = m_Recording->Info()->GetEvent();
#endif
m_Info = true;
if (m_Recording == NULL) {
m_Errno = 501;
asprintf(&m_Error, "Recording \"%s\" not found", Option);
}
}
else {
m_Errno = 501;
asprintf(&m_Error, "Error in Recording number \"%s\"", Option);
}
}
else if (Recordings.Count()) {
m_Traverse = true;
m_Index = 0;
m_Recording = Recordings.Get(m_Index);
if (m_Recording == NULL) {
m_Errno = 501;
asprintf(&m_Error, "Recording \"%d\" not found", m_Index + 1);
}
}
else {
m_Errno = 550;
m_Error = strdup("No recordings available");
}
}
cLSTRHandler::~cLSTRHandler()
{
if (m_Error != NULL)
free(m_Error);
}
bool cLSTRHandler::Next(bool &Last)
{
if (m_Error != NULL) {
Last = true;
cString str(m_Error, true);
m_Error = NULL;
return m_Client->Respond(m_Errno, *str);
}
if (m_Info) {
Last = false;
switch (m_State) {
case Recording:
if (m_Recording != NULL) {
m_State = Event;
return m_Client->Respond(-215, "C %s%s%s",
*m_Recording->Info()->ChannelID().ToString(),
m_Recording->Info()->ChannelName() ? " " : "",
m_Recording->Info()->ChannelName() ? m_Recording->Info()->ChannelName() : "");
}
else {
m_State = EndRecording;
return Next(Last);
}
break;
case Event:
m_State = Title;
if (m_Event != NULL) {
return m_Client->Respond(-215, "E %u %ld %d %X %X", (unsigned int) m_Event->EventID(),
m_Event->StartTime(), m_Event->Duration(),
m_Event->TableID(), m_Event->Version());
}
return Next(Last);
case Title:
m_State = Subtitle;
return m_Client->Respond(-215, "T %s", m_Recording->Info()->Title());
case Subtitle:
m_State = Description;
if (!isempty(m_Recording->Info()->ShortText())) {
return m_Client->Respond(-215, "S %s", m_Recording->Info()->ShortText());
}
return Next(Last);
case Description:
m_State = Components;
if (!isempty(m_Recording->Info()->Description())) {
m_State = Components;
char *copy = strdup(m_Recording->Info()->Description());
cString cpy(copy, true);
strreplace(copy, '\n', '|');
return m_Client->Respond(-215, "D %s", copy);
}
return Next(Last);
case Components:
if (m_Recording->Info()->Components()) {
if (m_CurrentComponent < m_Recording->Info()->Components()->NumComponents()) {
tComponent *p = m_Recording->Info()->Components()->Component(m_CurrentComponent);
m_CurrentComponent++;
if (!Setup.UseDolbyDigital && p->stream == 0x02 && p->type == 0x05)
return Next(Last);
return m_Client->Respond(-215, "X %s", *p->ToString());
}
}
m_State = Vps;
return Next(Last);
case Vps:
m_State = EndRecording;
if (m_Event != NULL) {
if (m_Event->Vps()) {
return m_Client->Respond(-215, "V %ld", m_Event->Vps());
}
}
return Next(Last);
case EndRecording:
Last = true;
return m_Client->Respond(215, "End of recording information");
}
}
else {
bool result;
Last = !m_Traverse || m_Index >= Recordings.Count() - 1;
result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Recording->Index() + 1, m_Recording->Title(' ', true));
if (m_Traverse && !Last) {
m_Recording = Recordings.Get(++m_Index);
if (m_Recording == NULL) {
m_Errno = 501;
asprintf(&m_Error, "Recording \"%d\" not found", m_Index + 1);
}
}
return result;
}
return false;
}
// --- cConnectionVTP --------------------------------------------------------- // --- cConnectionVTP ---------------------------------------------------------
cConnectionVTP::cConnectionVTP(void): cConnectionVTP::cConnectionVTP(void):
@ -476,12 +730,16 @@ cConnectionVTP::cConnectionVTP(void):
m_LiveStreamer(NULL), m_LiveStreamer(NULL),
m_FilterSocket(NULL), m_FilterSocket(NULL),
m_FilterStreamer(NULL), m_FilterStreamer(NULL),
m_RecSocket(NULL),
m_DataSocket(NULL),
m_LastCommand(NULL), m_LastCommand(NULL),
m_StreamType(stTSPIDS), m_StreamType(stTSPIDS),
m_FiltersSupport(false), m_FiltersSupport(false),
m_RecPlayer(NULL),
m_LSTEHandler(NULL), m_LSTEHandler(NULL),
m_LSTCHandler(NULL), m_LSTCHandler(NULL),
m_LSTTHandler(NULL) m_LSTTHandler(NULL),
m_LSTRHandler(NULL)
{ {
} }
@ -491,11 +749,15 @@ cConnectionVTP::~cConnectionVTP()
free(m_LastCommand); free(m_LastCommand);
delete m_LiveStreamer; delete m_LiveStreamer;
delete m_LiveSocket; delete m_LiveSocket;
delete m_RecSocket;
delete m_FilterStreamer; delete m_FilterStreamer;
delete m_FilterSocket; delete m_FilterSocket;
delete m_DataSocket;
delete m_LSTTHandler; delete m_LSTTHandler;
delete m_LSTCHandler; delete m_LSTCHandler;
delete m_LSTEHandler; delete m_LSTEHandler;
delete m_LSTRHandler;
delete m_RecPlayer;
} }
inline bool cConnectionVTP::Abort(void) const inline bool cConnectionVTP::Abort(void) const
@ -548,7 +810,7 @@ bool cConnectionVTP::Command(char *Cmd)
} }
if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param); if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param);
//else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param); else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param);
else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param); else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param);
else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param); else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param);
@ -561,7 +823,9 @@ bool cConnectionVTP::Command(char *Cmd)
if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param); if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param);
else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param); else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param);
else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param); else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param);
else if (strcasecmp(Cmd, "READ") == 0) return CmdREAD(param);
else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(param); else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(param);
else if (strcasecmp(Cmd, "PLAY") == 0) return CmdPLAY(param);
else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(param); else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(param);
else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param); else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param);
else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param); else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param);
@ -570,10 +834,17 @@ bool cConnectionVTP::Command(char *Cmd)
else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT(); else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT();
else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP(); else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP();
// Commands adopted from SVDRP // Commands adopted from SVDRP
//else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param); else if (strcasecmp(Cmd, "STAT") == 0) return CmdSTAT(param);
else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(param); else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(param);
else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(param); else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(param);
else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(param); else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(param);
else if (strcasecmp(Cmd, "NEXT") == 0) return CmdNEXT(param);
else if (strcasecmp(Cmd, "NEWC") == 0) return CmdNEWC(param);
else if (strcasecmp(Cmd, "MODC") == 0) return CmdMODC(param);
else if (strcasecmp(Cmd, "MOVC") == 0) return CmdMOVC(param);
else if (strcasecmp(Cmd, "DELC") == 0) return CmdDELC(param);
else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param);
else if (strcasecmp(Cmd, "RENR") == 0) return CmdRENR(param);
else else
return Respond(500, "Unknown Command \"%s\"", Cmd); return Respond(500, "Unknown Command \"%s\"", Cmd);
} }
@ -595,12 +866,10 @@ bool cConnectionVTP::CmdCAPS(char *Opts)
return Respond(220, "Capability \"%s\" accepted", Opts); return Respond(220, "Capability \"%s\" accepted", Opts);
} }
#if APIVERSNUM < 10703
if (strcasecmp(Opts, "PES") == 0) { if (strcasecmp(Opts, "PES") == 0) {
m_StreamType = stPES; m_StreamType = stPES;
return Respond(220, "Capability \"%s\" accepted", Opts); return Respond(220, "Capability \"%s\" accepted", Opts);
} }
#endif
if (strcasecmp(Opts, "EXTERN") == 0) { if (strcasecmp(Opts, "EXTERN") == 0) {
m_StreamType = stExtern; m_StreamType = stExtern;
@ -648,7 +917,7 @@ bool cConnectionVTP::CmdPORT(char *Opts)
if (ep == Opts || !isspace(*ep)) if (ep == Opts || !isspace(*ep))
return Respond(500, "Use: PORT Id Destination"); return Respond(500, "Use: PORT Id Destination");
if (id != siLive && id != siLiveFilter) if (id >= si_Count)
return Respond(501, "Wrong connection id %d", id); return Respond(501, "Wrong connection id %d", id);
Opts = skipspace(ep); Opts = skipspace(ep);
@ -676,7 +945,8 @@ bool cConnectionVTP::CmdPORT(char *Opts)
isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport); isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport);
if (id == siLiveFilter) { switch (id) {
case siLiveFilter:
m_FiltersSupport = true; m_FiltersSupport = true;
if(m_FilterStreamer) if(m_FilterStreamer)
m_FilterStreamer->Stop(); m_FilterStreamer->Stop();
@ -696,8 +966,9 @@ bool cConnectionVTP::CmdPORT(char *Opts)
m_FilterStreamer->Activate(true); m_FilterStreamer->Activate(true);
return Respond(220, "Port command ok, data connection opened"); return Respond(220, "Port command ok, data connection opened");
} break;
case siLive:
if(m_LiveSocket && m_LiveStreamer) if(m_LiveSocket && m_LiveStreamer)
m_LiveStreamer->Stop(); m_LiveStreamer->Stop();
delete m_LiveSocket; delete m_LiveSocket;
@ -716,6 +987,70 @@ bool cConnectionVTP::CmdPORT(char *Opts)
m_LiveStreamer->Start(m_LiveSocket); m_LiveStreamer->Start(m_LiveSocket);
return Respond(220, "Port command ok, data connection opened"); return Respond(220, "Port command ok, data connection opened");
break;
case siReplay:
delete m_RecSocket;
m_RecSocket = new cTBSocket(SOCK_STREAM);
if (!m_RecSocket->Connect(dataip, dataport)) {
esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
dataip, dataport, strerror(errno));
DELETENULL(m_RecSocket);
return Respond(551, "Couldn't open data connection");
}
if (!m_RecSocket->SetDSCP())
LOG_ERROR_STR("unable to set DSCP sockopt");
return Respond(220, "Port command ok, data connection opened");
break;
case siDataRespond:
delete m_DataSocket;
m_DataSocket = new cTBSocket(SOCK_STREAM);
if (!m_DataSocket->Connect(dataip, dataport)) {
esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
dataip, dataport, strerror(errno));
DELETENULL(m_DataSocket);
return Respond(551, "Couldn't open data connection");
}
return Respond(220, "Port command ok, data connection opened");
break;
default:
return Respond(501, "No handler for id %u", id);
}
}
bool cConnectionVTP::CmdREAD(char *Opts)
{
if (*Opts) {
char *tail;
uint64_t position = strtoll(Opts, &tail, 10);
if (tail && tail != Opts) {
tail = skipspace(tail);
if (tail && tail != Opts) {
int size = strtol(tail, NULL, 10);
uint8_t* data = (uint8_t*)malloc(size+4);
unsigned long count_readed = m_RecPlayer->getBlock(data, position, size);
unsigned long count_written = m_RecSocket->SysWrite(data, count_readed);
free(data);
return Respond(220, "%lu Bytes submitted", count_written);
}
else {
return Respond(501, "Missing position");
}
}
else {
return Respond(501, "Missing size");
}
}
else {
return Respond(501, "Missing position");
}
} }
bool cConnectionVTP::CmdTUNE(char *Opts) bool cConnectionVTP::CmdTUNE(char *Opts)
@ -749,6 +1084,32 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
return Respond(220, "Channel tuned"); return Respond(220, "Channel tuned");
} }
bool cConnectionVTP::CmdPLAY(char *Opts)
{
Recordings.Update(true);
if (*Opts) {
if (isnumber(Opts)) {
cRecording *recording = Recordings.Get(strtol(Opts, NULL, 10) - 1);
if (recording) {
if (m_RecPlayer) {
delete m_RecPlayer;
}
m_RecPlayer = new RecPlayer(recording);
return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecPlayer->getLengthBytes(), (unsigned int) m_RecPlayer->getLengthFrames());
}
else {
return Respond(550, "Recording \"%s\" not found", Opts);
}
}
else {
return Respond(500, "Use: PLAY record");
}
}
else {
return Respond(500, "Use: PLAY record");
}
}
bool cConnectionVTP::CmdADDP(char *Opts) bool cConnectionVTP::CmdADDP(char *Opts)
{ {
int pid; int pid;
@ -844,6 +1205,13 @@ bool cConnectionVTP::CmdABRT(char *Opts)
DELETENULL(m_FilterStreamer); DELETENULL(m_FilterStreamer);
DELETENULL(m_FilterSocket); DELETENULL(m_FilterSocket);
break; break;
case siReplay:
DELETENULL(m_RecPlayer);
DELETENULL(m_RecSocket);
break;
case siDataRespond:
DELETENULL(m_DataSocket);
break;
default: default:
return Respond(501, "Wrong connection id %d", id); return Respond(501, "Wrong connection id %d", id);
break; break;
@ -881,7 +1249,8 @@ bool cConnectionVTP::CmdLSTX(cHandler *&Handler, char *Option)
Handler = new cHandler(this, Option); Handler = new cHandler(this, Option);
} }
bool last, result = false; bool last = false;
bool result = false;
if (Handler != NULL) if (Handler != NULL)
result = Handler->Next(last); result = Handler->Next(last);
else else
@ -907,11 +1276,66 @@ bool cConnectionVTP::CmdLSTT(char *Option)
return CmdLSTX(m_LSTTHandler, Option); return CmdLSTX(m_LSTTHandler, Option);
} }
bool cConnectionVTP::CmdLSTR(char *Option)
{
return CmdLSTX(m_LSTRHandler, Option);
}
// Functions adopted from SVDRP // Functions adopted from SVDRP
#define INIT_WRAPPER() bool _res #define INIT_WRAPPER() bool _res
#define Reply(c,m...) _res = Respond(c,m) #define Reply(c,m...) _res = Respond(c,m)
#define EXIT_WRAPPER() return _res #define EXIT_WRAPPER() return _res
bool cConnectionVTP::CmdSTAT(const char *Option)
{
INIT_WRAPPER();
if (*Option) {
if (strcasecmp(Option, "DISK") == 0) {
int FreeMB, UsedMB;
int Percent = VideoDiskSpace(&FreeMB, &UsedMB);
Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
}
else if (strcasecmp(Option, "NAME") == 0) {
Reply(250, "vdr - The Video Disk Recorder with Streamdev-Server");
}
else if (strcasecmp(Option, "VERSION") == 0) {
Reply(250, "VDR: %s | Streamdev: %s", VDRVERSION, VERSION);
}
else if (strcasecmp(Option, "RECORDS") == 0) {
bool recordings = Recordings.Load();
Recordings.Sort();
if (recordings) {
cRecording *recording = Recordings.Last();
Reply(250, "%d", recording->Index() + 1);
}
else {
Reply(250, "0");
}
}
else if (strcasecmp(Option, "CHANNELS") == 0) {
Reply(250, "%d", Channels.MaxNumber());
}
else if (strcasecmp(Option, "TIMERS") == 0) {
Reply(250, "%d", Timers.Count());
}
else if (strcasecmp(Option, "CHARSET") == 0) {
Reply(250, "%s", cCharSetConv::SystemCharacterTable());
}
else if (strcasecmp(Option, "TIME") == 0) {
time_t timeNow = time(NULL);
struct tm* timeStruct = localtime(&timeNow);
int timeOffset = timeStruct->tm_gmtoff;
Reply(250, "%lu %i", (unsigned long) timeNow, timeOffset);
}
else
Reply(501, "Invalid Option \"%s\"", Option);
}
else
Reply(501, "No option given");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdMODT(const char *Option) bool cConnectionVTP::CmdMODT(const char *Option)
{ {
INIT_WRAPPER(); INIT_WRAPPER();
@ -992,51 +1416,235 @@ bool cConnectionVTP::CmdDELT(const char *Option)
EXIT_WRAPPER(); EXIT_WRAPPER();
} }
/*bool cConnectionVTP::CmdLSTR(char *Option) { bool cConnectionVTP::CmdNEXT(const char *Option)
{
INIT_WRAPPER(); INIT_WRAPPER();
bool recordings = Recordings.Load(); cTimer *t = Timers.GetNextActiveTimer();
Recordings.Sort(); if (t) {
if (*Option) { time_t Start = t->StartTime();
if (isnumber(Option)) { int Number = t->Index() + 1;
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); if (!*Option)
if (recording) { Reply(250, "%d %s", Number, *TimeToString(Start));
if (recording->Summary()) { else if (strcasecmp(Option, "ABS") == 0)
char *summary = strdup(recording->Summary()); Reply(250, "%d %ld", Number, Start);
Reply(250, "%s", strreplace(summary,'\n','|')); else if (strcasecmp(Option, "REL") == 0)
free(summary); Reply(250, "%d %ld", Number, Start - time(NULL));
else
Reply(501, "Unknown option: \"%s\"", Option);
} }
else else
Reply(550, "No summary availabe"); Reply(550, "No active timers");
}
else
Reply(550, "Recording \"%s\" not found", Option);
}
else
Reply(501, "Error in recording number \"%s\"", Option);
}
else if (recordings) {
cRecording *recording = Recordings.First();
while (recording) {
Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
recording = Recordings.Next(recording);
}
}
else
Reply(550, "No recordings available");
EXIT_WRAPPER(); EXIT_WRAPPER();
} }
bool cConnectionVTP::CmdDELR(char *Option) { bool cConnectionVTP::CmdNEWC(const char *Option)
{
INIT_WRAPPER();
if (*Option) {
cChannel ch;
if (ch.Parse(Option)) {
if (Channels.HasUniqueChannelID(&ch)) {
cChannel *channel = new cChannel;
*channel = ch;
Channels.Add(channel);
Channels.ReNumber();
Channels.SetModified(true);
isyslog("new channel %d %s", channel->Number(), *channel->ToText());
Reply(250, "%d %s", channel->Number(), *channel->ToText());
}
else {
Reply(501, "Channel settings are not unique");
}
}
else {
Reply(501, "Error in channel settings");
}
}
else {
Reply(501, "Missing channel settings");
}
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdMODC(const char *Option)
{
INIT_WRAPPER();
if (*Option) {
char *tail;
int n = strtol(Option, &tail, 10);
if (tail && tail != Option) {
tail = skipspace(tail);
if (!Channels.BeingEdited()) {
cChannel *channel = Channels.GetByNumber(n);
if (channel) {
cChannel ch;
if (ch.Parse(tail)) {
if (Channels.HasUniqueChannelID(&ch, channel)) {
*channel = ch;
Channels.ReNumber();
Channels.SetModified(true);
isyslog("modifed channel %d %s", channel->Number(), *channel->ToText());
Reply(250, "%d %s", channel->Number(), *channel->ToText());
}
else {
Reply(501, "Channel settings are not unique");
}
}
else {
Reply(501, "Error in channel settings");
}
}
else {
Reply(501, "Channel \"%d\" not defined", n);
}
}
else {
Reply(550, "Channels are being edited - try again later");
}
}
else {
Reply(501, "Error in channel number");
}
}
else {
Reply(501, "Missing channel settings");
}
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdMOVC(const char *Option)
{
INIT_WRAPPER();
if (*Option) {
if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
char *tail;
int From = strtol(Option, &tail, 10);
if (tail && tail != Option) {
tail = skipspace(tail);
if (tail && tail != Option) {
int To = strtol(tail, NULL, 10);
int CurrentChannelNr = cDevice::CurrentChannel();
cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
cChannel *FromChannel = Channels.GetByNumber(From);
if (FromChannel) {
cChannel *ToChannel = Channels.GetByNumber(To);
if (ToChannel) {
int FromNumber = FromChannel->Number();
int ToNumber = ToChannel->Number();
if (FromNumber != ToNumber) {
Channels.Move(FromChannel, ToChannel);
Channels.ReNumber();
Channels.SetModified(true);
if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
Channels.SwitchTo(CurrentChannel->Number());
}
else {
cDevice::SetCurrentChannel(CurrentChannel);
}
}
isyslog("channel %d moved to %d", FromNumber, ToNumber);
Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
}
else {
Reply(501, "Can't move channel to same postion");
}
}
else {
Reply(501, "Channel \"%d\" not defined", To);
}
}
else {
Reply(501, "Channel \"%d\" not defined", From);
}
}
else {
Reply(501, "Error in channel number");
}
}
else {
Reply(501, "Error in channel number");
}
}
else {
Reply(550, "Channels or timers are being edited - try again later");
}
}
else {
Reply(501, "Missing channel number");
}
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdDELC(const char *Option)
{
INIT_WRAPPER();
if (*Option) {
if (isnumber(Option)) {
if (!Channels.BeingEdited()) {
cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
if (channel) {
for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
if (timer->Channel() == channel) {
Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
return false;
}
}
int CurrentChannelNr = cDevice::CurrentChannel();
cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
if (CurrentChannel && channel == CurrentChannel) {
int n = Channels.GetNextNormal(CurrentChannel->Index());
if (n < 0)
n = Channels.GetPrevNormal(CurrentChannel->Index());
CurrentChannel = Channels.Get(n);
CurrentChannelNr = 0; // triggers channel switch below
}
Channels.Del(channel);
Channels.ReNumber();
Channels.SetModified(true);
isyslog("channel %s deleted", Option);
if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
Channels.SwitchTo(CurrentChannel->Number());
else
cDevice::SetCurrentChannel(CurrentChannel);
}
Reply(250, "Channel \"%s\" deleted", Option);
}
else
Reply(501, "Channel \"%s\" not defined", Option);
}
else
Reply(550, "Channels are being edited - try again later");
}
else
Reply(501, "Error in channel number \"%s\"", Option);
}
else {
Reply(501, "Missing channel number");
}
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdDELR(const char *Option)
{
INIT_WRAPPER(); INIT_WRAPPER();
if (*Option) { if (*Option) {
if (isnumber(Option)) { if (isnumber(Option)) {
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
if (recording) { if (recording) {
if (recording->Delete()) cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
if (!rc) {
if (recording->Delete()) {
Reply(250, "Recording \"%s\" deleted", Option); Reply(250, "Recording \"%s\" deleted", Option);
::Recordings.DelByName(recording->FileName());
}
else else
Reply(554, "Error while deleting recording!"); Reply(554, "Error while deleting recording!");
} }
else
Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1);
}
else else
Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)"); Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
} }
@ -1046,7 +1654,55 @@ bool cConnectionVTP::CmdDELR(char *Option) {
else else
Reply(501, "Missing recording number"); Reply(501, "Missing recording number");
EXIT_WRAPPER(); EXIT_WRAPPER();
}*/ }
bool cConnectionVTP::CmdRENR(const char *Option)
{
INIT_WRAPPER();
#if defined(LIEMIKUUTIO)
bool recordings = Recordings.Update(true);
if (recordings) {
if (*Option) {
char *tail;
int n = strtol(Option, &tail, 10);
cRecording *recording = Recordings.Get(n - 1);
if (recording && tail && tail != Option) {
#if APIVERSNUM < 10704
int priority = recording->priority;
int lifetime = recording->lifetime;
#endif
char *oldName = strdup(recording->Name());
tail = skipspace(tail);
#if APIVERSNUM < 10704
if (recording->Rename(tail, &priority, &lifetime)) {
#else
if (recording->Rename(tail)) {
#endif
Reply(250, "Renamed \"%s\" to \"%s\"", oldName, recording->Name());
Recordings.ChangeState();
Recordings.TouchUpdate();
}
else {
Reply(501, "Renaming \"%s\" to \"%s\" failed", oldName, tail);
}
free(oldName);
}
else {
Reply(501, "Recording not found or wrong syntax");
}
}
else {
Reply(501, "Missing Input settings");
}
}
else {
Reply(550, "No recordings available");
}
#else
Reply(501, "Rename not supported, please use LIEMIEXT");
#endif /* LIEMIKUUTIO */
EXIT_WRAPPER();
}
bool cConnectionVTP::Respond(int Code, const char *Message, ...) bool cConnectionVTP::Respond(int Code, const char *Message, ...)
{ {

View File

@ -2,6 +2,7 @@
#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H #define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
#include "server/connection.h" #include "server/connection.h"
#include "server/recplayer.h"
class cTBSocket; class cTBSocket;
class cStreamdevLiveStreamer; class cStreamdevLiveStreamer;
@ -9,6 +10,7 @@ class cStreamdevFilterStreamer;
class cLSTEHandler; class cLSTEHandler;
class cLSTCHandler; class cLSTCHandler;
class cLSTTHandler; class cLSTTHandler;
class cLSTRHandler;
class cConnectionVTP: public cServerConnection { class cConnectionVTP: public cServerConnection {
friend class cLSTEHandler; friend class cLSTEHandler;
@ -21,16 +23,19 @@ private:
cStreamdevLiveStreamer *m_LiveStreamer; cStreamdevLiveStreamer *m_LiveStreamer;
cTBSocket *m_FilterSocket; cTBSocket *m_FilterSocket;
cStreamdevFilterStreamer *m_FilterStreamer; cStreamdevFilterStreamer *m_FilterStreamer;
cTBSocket *m_RecSocket;
cTBSocket *m_DataSocket;
char *m_LastCommand; char *m_LastCommand;
eStreamType m_StreamType; eStreamType m_StreamType;
bool m_FiltersSupport; bool m_FiltersSupport;
RecPlayer *m_RecPlayer;
// Members adopted for SVDRP // Members adopted for SVDRP
cRecordings Recordings;
cLSTEHandler *m_LSTEHandler; cLSTEHandler *m_LSTEHandler;
cLSTCHandler *m_LSTCHandler; cLSTCHandler *m_LSTCHandler;
cLSTTHandler *m_LSTTHandler; cLSTTHandler *m_LSTTHandler;
cLSTRHandler *m_LSTRHandler;
protected: protected:
template<class cHandler> template<class cHandler>
@ -51,7 +56,9 @@ public:
bool CmdCAPS(char *Opts); bool CmdCAPS(char *Opts);
bool CmdPROV(char *Opts); bool CmdPROV(char *Opts);
bool CmdPORT(char *Opts); bool CmdPORT(char *Opts);
bool CmdREAD(char *Opts);
bool CmdTUNE(char *Opts); bool CmdTUNE(char *Opts);
bool CmdPLAY(char *Opts);
bool CmdADDP(char *Opts); bool CmdADDP(char *Opts);
bool CmdDELP(char *Opts); bool CmdDELP(char *Opts);
bool CmdADDF(char *Opts); bool CmdADDF(char *Opts);
@ -64,14 +71,20 @@ public:
bool CmdLSTE(char *Opts); bool CmdLSTE(char *Opts);
bool CmdLSTC(char *Opts); bool CmdLSTC(char *Opts);
bool CmdLSTT(char *Opts); bool CmdLSTT(char *Opts);
bool CmdLSTR(char *Opts);
// Commands adopted from SVDRP // Commands adopted from SVDRP
bool CmdSTAT(const char *Option);
bool CmdMODT(const char *Option); bool CmdMODT(const char *Option);
bool CmdNEWT(const char *Option); bool CmdNEWT(const char *Option);
bool CmdDELT(const char *Option); bool CmdDELT(const char *Option);
bool CmdNEXT(const char *Option);
//bool CmdLSTR(char *Opts); bool CmdNEWC(const char *Option);
//bool CmdDELR(char *Opts); 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, ...) bool Respond(int Code, const char *Message, ...)
__attribute__ ((format (printf, 3, 4))); __attribute__ ((format (printf, 3, 4)));

View File

@ -4,6 +4,7 @@
#include <libsi/descriptor.h> #include <libsi/descriptor.h>
#include "remux/ts2ps.h" #include "remux/ts2ps.h"
#include "remux/ts2pes.h"
#include "remux/ts2es.h" #include "remux/ts2es.h"
#include "remux/extern.h" #include "remux/extern.h"
@ -13,7 +14,7 @@
#include "server/livefilter.h" #include "server/livefilter.h"
#include "common.h" #include "common.h"
#define TSPATREPACKER using namespace Streamdev;
// --- cStreamdevLiveReceiver ------------------------------------------------- // --- cStreamdevLiveReceiver -------------------------------------------------
@ -64,6 +65,8 @@ private:
int pmtPid; int pmtPid;
int pmtSid; int pmtSid;
int pmtVersion; int pmtVersion;
uchar tspat_buf[TS_SIZE];
cStreamdevBuffer siBuffer;
const cChannel *m_Channel; const cChannel *m_Channel;
cStreamdevLiveStreamer *m_Streamer; cStreamdevLiveStreamer *m_Streamer;
@ -73,9 +76,11 @@ private:
int GetPid(SI::PMT::Stream& stream); int GetPid(SI::PMT::Stream& stream);
public: public:
cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel); 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\")\n", Channel->Name()); Dprintf("cStreamdevPatFilter(\"%s\")\n", Channel->Name());
assert(Streamer); assert(Streamer);
@ -85,6 +90,29 @@ cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const
pmtSid = 0; pmtSid = 0;
pmtVersion = -1; pmtVersion = -1;
Set(0x00, 0x00); // PAT 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[] = { static const char * const psStreamTypes[] = {
@ -224,34 +252,18 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
if (0 != (pmtPid = assoc.getPid())) { if (0 != (pmtPid = assoc.getPid())) {
Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d\n", Channel->Name(), pmtPid); Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d\n", Channel->Name(), pmtPid);
pmtSid = assoc.getServiceId(); pmtSid = assoc.getServiceId();
if (Length < TS_SIZE-5) {
// repack PAT to TS frame and send to client // 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; int ts_id;
unsigned int crc, i, len; unsigned int crc, i, len;
uint8_t *tmp, tspat_buf[TS_SIZE]; uint8_t *tmp;
static uint8_t ccounter = 0; static uint8_t ccounter = 0;
ccounter = (ccounter + 1) % 16; ccounter = (ccounter + 1) % 16;
memset(tspat_buf, 0xff, TS_SIZE);
ts_id = Channel->Tid(); // Get transport stream id of the channel 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 | ccounter; // Set payload flag, Continuity counter tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter
tspat_buf[4] = 0x0; // SI pointer field
tspat_buf[5] = 0x0; // PAT table id
tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set
tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1
tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15) tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15)
tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7) tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7)
tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) | tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) |
pat.getCurrentNextIndicator();// Version number, Current next indicator pat.getCurrentNextIndicator();// Version number, Current next indicator
tspat_buf[11] = 0x0; // Section number
tspat_buf[12] = 0x0; // Last section number
tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15) tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15)
tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7) tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7)
tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12) tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12)
@ -268,10 +280,9 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
tspat_buf[18] = crc >> 16 & 0xff; // Checksum tspat_buf[18] = crc >> 16 & 0xff; // Checksum
tspat_buf[19] = crc >> 8 & 0xff; // Checksum tspat_buf[19] = crc >> 8 & 0xff; // Checksum
tspat_buf[20] = crc & 0xff; // Checksum tspat_buf[20] = crc & 0xff; // Checksum
m_Streamer->Put(tspat_buf, TS_SIZE); int written = siBuffer.PutTS(tspat_buf, TS_SIZE);
#endif if (written != TS_SIZE)
} else siBuffer.ReportOverflow(TS_SIZE - written);
isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length);
if (pmtPid != prevPmtPid) { if (pmtPid != prevPmtPid) {
m_Streamer->SetPids(pmtPid); m_Streamer->SetPids(pmtPid);
Add(pmtPid, 0x02); Add(pmtPid, 0x02);
@ -292,7 +303,7 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
if (pmtVersion != -1) { if (pmtVersion != -1) {
if (pmtVersion != pmt.getVersionNumber()) { if (pmtVersion != pmt.getVersionNumber()) {
Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n"); Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n");
Del(pmtPid, 0x02); cFilter::Del(pmtPid, 0x02);
pmtPid = 0; // this triggers PAT scan pmtPid = 0; // this triggers PAT scan
} }
return; return;
@ -329,12 +340,7 @@ cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Paramet
m_Device(NULL), m_Device(NULL),
m_Receiver(NULL), m_Receiver(NULL),
m_PatFilter(NULL), m_PatFilter(NULL),
#if APIVERSNUM < 10703 m_Remux(NULL)
m_PESRemux(NULL),
#endif
m_ESRemux(NULL),
m_PSRemux(NULL),
m_ExtRemux(NULL)
{ {
} }
@ -347,12 +353,7 @@ cStreamdevLiveStreamer::~cStreamdevLiveStreamer()
DELETENULL(m_PatFilter); DELETENULL(m_PatFilter);
} }
DELETENULL(m_Receiver); DELETENULL(m_Receiver);
#if APIVERSNUM < 10703 delete m_Remux;
delete m_PESRemux;
#endif
delete m_ESRemux;
delete m_PSRemux;
delete m_ExtRemux;
} }
bool cStreamdevLiveStreamer::HasPid(int Pid) bool cStreamdevLiveStreamer::HasPid(int Pid)
@ -459,19 +460,17 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid(); int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid();
if (Apid != 0) if (Apid != 0)
pid = Apid; pid = Apid;
m_ESRemux = new cTS2ESRemux(pid); m_Remux = new cTS2ESRemux(pid);
return SetPids(pid); return SetPids(pid);
} }
#if APIVERSNUM < 10703
case stPES: case stPES:
m_PESRemux = new cRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), m_Remux = new cTS2PESRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids(), false); m_Channel->Spids());
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
#endif
case stPS: case stPS:
m_PSRemux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), m_Remux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids()); m_Channel->Spids());
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
@ -490,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
return true; return true;
case stExtern: case stExtern:
m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), m_Remux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids(), m_Parameter); m_Channel->Spids(), m_Parameter);
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
@ -503,82 +502,39 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
int cStreamdevLiveStreamer::Put(const uchar *Data, int Count) int cStreamdevLiveStreamer::Put(const uchar *Data, int Count)
{ {
switch (m_StreamType) { // insert si data
case stTS: if (m_PatFilter) {
case stTSPIDS: int siCount;
return cStreamdevStreamer::Put(Data, Count); uchar *siData = m_PatFilter->Get(siCount);
if (siData) {
#if APIVERSNUM < 10703 if (m_Remux)
case stPES: siCount = m_Remux->Put(siData, siCount);
return m_PESRemux->Put(Data, Count); else
#endif siCount = cStreamdevStreamer::Put(siData, siCount);
if (siCount)
case stES: m_PatFilter->Del(siCount);
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;
} }
} }
if (m_Remux)
return m_Remux->Put(Data, Count);
else
return cStreamdevStreamer::Put(Data, Count);
}
uchar *cStreamdevLiveStreamer::Get(int &Count) uchar *cStreamdevLiveStreamer::Get(int &Count)
{ {
switch (m_StreamType) { if (m_Remux)
case stTS: return m_Remux->Get(Count);
case stTSPIDS: else
return cStreamdevStreamer::Get(Count); return cStreamdevStreamer::Get(Count);
#if APIVERSNUM < 10703
case stPES:
return m_PESRemux->Get(Count);
#endif
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) void cStreamdevLiveStreamer::Del(int Count)
{ {
switch (m_StreamType) { if (m_Remux)
case stTS: m_Remux->Del(Count);
case stTSPIDS: else
cStreamdevStreamer::Del(Count); cStreamdevStreamer::Del(Count);
break;
#if APIVERSNUM < 10703
case stPES:
m_PESRemux->Del(Count);
break;
#endif
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) void cStreamdevLiveStreamer::Attach(void)

View File

@ -5,14 +5,12 @@
#include <vdr/receiver.h> #include <vdr/receiver.h>
#include "server/streamer.h" #include "server/streamer.h"
#include "remux/tsremux.h"
#include "common.h" #include "common.h"
class cTS2PSRemux; namespace Streamdev {
class cTS2ESRemux; class cTSRemux;
class cExternRemux; }
#if APIVERSNUM < 10703
class cRemux;
#endif
class cStreamdevPatFilter; class cStreamdevPatFilter;
class cStreamdevLiveReceiver; class cStreamdevLiveReceiver;
@ -29,12 +27,7 @@ private:
cDevice *m_Device; cDevice *m_Device;
cStreamdevLiveReceiver *m_Receiver; cStreamdevLiveReceiver *m_Receiver;
cStreamdevPatFilter *m_PatFilter; cStreamdevPatFilter *m_PatFilter;
#if APIVERSNUM < 10703 Streamdev::cTSRemux *m_Remux;
cRemux *m_PESRemux;
#endif
cTS2ESRemux *m_ESRemux;
cTS2PSRemux *m_PSRemux;
cExternRemux *m_ExtRemux;
void StartReceiver(void); void StartReceiver(void);
bool HasPid(int Pid); bool HasPid(int Pid);

View File

@ -201,10 +201,8 @@ std::string cHtmlChannelList::StreamTypeMenu()
(std::string) "[<a href=\"/TS/" + self + "\">TS</a>] "); (std::string) "[<a href=\"/TS/" + self + "\">TS</a>] ");
typeMenu += (streamType == stPS ? (std::string) "[PS] " : typeMenu += (streamType == stPS ? (std::string) "[PS] " :
(std::string) "[<a href=\"/PS/" + self + "\">PS</a>] "); (std::string) "[<a href=\"/PS/" + self + "\">PS</a>] ");
#if APIVERSNUM < 10703
typeMenu += (streamType == stPES ? (std::string) "[PES] " : typeMenu += (streamType == stPES ? (std::string) "[PES] " :
(std::string) "[<a href=\"/PES/" + self + "\">PES</a>] "); (std::string) "[<a href=\"/PES/" + self + "\">PES</a>] ");
#endif
typeMenu += (streamType == stES ? (std::string) "[ES] " : typeMenu += (streamType == stES ? (std::string) "[ES] " :
(std::string) "[<a href=\"/ES/" + self + "\">ES</a>] "); (std::string) "[<a href=\"/ES/" + self + "\">ES</a>] ");
typeMenu += (streamType == stExtern ? (std::string) "[Extern] " : typeMenu += (streamType == stExtern ? (std::string) "[Extern] " :
@ -343,10 +341,8 @@ std::string cHtmlChannelList::ItemText()
switch (streamType) { switch (streamType) {
case stTS: suffix = (std::string) ".ts"; break; case stTS: suffix = (std::string) ".ts"; break;
case stPS: suffix = (std::string) ".vob"; break; case stPS: suffix = (std::string) ".vob"; break;
#if APIVERSNUM < 10703
// for Network Media Tank // for Network Media Tank
case stPES: suffix = (std::string) ".vdr"; break; case stPES: suffix = (std::string) ".vdr"; break;
#endif
default: suffix = ""; default: suffix = "";
} }
line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">"; line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";

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,5 +1,5 @@
/* /*
* $Id: streamer.c,v 1.18 2009/02/13 10:39:22 schmirl Exp $ * $Id: streamer.c,v 1.19 2009/06/19 06:32:45 schmirl Exp $
*/ */
#include <vdr/ringbuffer.h> #include <vdr/ringbuffer.h>
@ -14,6 +14,13 @@
#include "tools/select.h" #include "tools/select.h"
#include "common.h" #include "common.h"
// --- cStreamdevBuffer -------------------------------------------------------
cStreamdevBuffer::cStreamdevBuffer(int Size, int Margin, bool Statistics, const char *Description):
cRingBufferLinear(Size, Margin, Statistics, Description)
{
}
// --- cStreamdevWriter ------------------------------------------------------- // --- cStreamdevWriter -------------------------------------------------------
cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket, cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket,
@ -95,14 +102,13 @@ void cStreamdevWriter::Action(void)
cStreamdevStreamer::cStreamdevStreamer(const char *Name): cStreamdevStreamer::cStreamdevStreamer(const char *Name):
cThread(Name), cThread(Name),
m_Running(false),
m_Writer(NULL), m_Writer(NULL),
m_RingBuffer(new cRingBufferLinear(STREAMERBUFSIZE, TS_SIZE * 2, m_RingBuffer(new cStreamdevBuffer(STREAMERBUFSIZE, TS_SIZE * 2,
true, "streamdev-streamer")), 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_RingBuffer->SetTimeouts(0, 100);
m_SendBuffer->SetTimeouts(0, 100); m_SendBuffer->SetTimeouts(100, 100);
} }
cStreamdevStreamer::~cStreamdevStreamer() cStreamdevStreamer::~cStreamdevStreamer()
@ -116,7 +122,6 @@ void cStreamdevStreamer::Start(cTBSocket *Socket)
{ {
Dprintf("start streamer\n"); Dprintf("start streamer\n");
m_Writer = new cStreamdevWriter(Socket, this); m_Writer = new cStreamdevWriter(Socket, this);
m_Running = true;
Attach(); Attach();
} }
@ -135,9 +140,8 @@ void cStreamdevStreamer::Stop(void)
Dprintf("stopping streamer\n"); Dprintf("stopping streamer\n");
Cancel(3); Cancel(3);
} }
if (m_Running) { if (m_Writer) {
Detach(); Detach();
m_Running = false;
DELETENULL(m_Writer); DELETENULL(m_Writer);
} }
} }
@ -152,8 +156,6 @@ void cStreamdevStreamer::Action(void)
int count = Put(block, got); int count = Put(block, got);
if (count) if (count)
m_RingBuffer->Del(count); m_RingBuffer->Del(count);
else
cCondWait::SleepMs(100);
} }
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* $Id: streamer.h,v 1.10 2009/02/13 10:39:22 schmirl Exp $ * $Id: streamer.h,v 1.11 2009/06/19 06:32:45 schmirl Exp $
*/ */
#ifndef VDR_STREAMDEV_STREAMER_H #ifndef VDR_STREAMDEV_STREAMER_H
@ -16,8 +16,34 @@ class cStreamdevStreamer;
#define TS_SIZE 188 #define TS_SIZE 188
#endif #endif
#define STREAMERBUFSIZE MEGABYTE(4) #define STREAMERBUFSIZE (20000 * TS_SIZE)
#define WRITERBUFSIZE KILOBYTE(256) #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 ------------------------------------------------------- // --- cStreamdevWriter -------------------------------------------------------
@ -38,15 +64,14 @@ public:
class cStreamdevStreamer: public cThread { class cStreamdevStreamer: public cThread {
private: private:
bool m_Running;
cStreamdevWriter *m_Writer; cStreamdevWriter *m_Writer;
cRingBufferLinear *m_RingBuffer; cStreamdevBuffer *m_RingBuffer;
cRingBufferLinear *m_SendBuffer; cStreamdevBuffer *m_SendBuffer;
protected: protected:
virtual void Action(void); virtual void Action(void);
bool IsRunning(void) const { return m_Running; } bool IsRunning(void) const { return m_Writer; }
public: public:
cStreamdevStreamer(const char *Name); cStreamdevStreamer(const char *Name);
@ -57,10 +82,10 @@ public:
bool Abort(void); bool Abort(void);
void Activate(bool On); 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); } 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 uchar *Get(int &Count) { return m_SendBuffer->Get(Count); }
virtual void Del(int Count) { m_SendBuffer->Del(Count); } virtual void Del(int Count) { m_SendBuffer->Del(Count); }

View File

@ -3,12 +3,11 @@
* *
* See the README file for copyright information and how to reach the author. * See the README file for copyright information and how to reach the author.
* *
* $Id: streamdev-server.c,v 1.11 2008/10/14 11:05:47 schmirl Exp $ * $Id: streamdev-server.c,v 1.12 2009/06/19 06:32:38 schmirl Exp $
*/ */
#include <getopt.h> #include <getopt.h>
#include <vdr/tools.h> #include <vdr/tools.h>
#include "remux/extern.h"
#include "streamdev-server.h" #include "streamdev-server.h"
#include "server/setup.h" #include "server/setup.h"
#include "server/server.h" #include "server/server.h"