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
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
@ -30,6 +34,7 @@ Rolf Ahrenberg
for replacing private members by cThread::Running()/Active()
for improving externremux script termination
for fixing PAT repacker version field
for correcting LIMIKUUTIO patch detection
Rantanen Teemu
for providing vdr-incompletesections.diff
@ -74,6 +79,7 @@ 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
@ -111,3 +117,6 @@ Joachim K
Artem Makhutov
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
---------------------------------------
- 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
shutting down VDR
- added IGMP based multicast streaming

View File

@ -1,7 +1,7 @@
#
# 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.
# 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/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 \
remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.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

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
@ -54,6 +54,9 @@ public:
virtual bool ProvidesTransponder(const cChannel *Channel) const;
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1,
bool *NeedsDetachReceivers = NULL) const;
#if APIVERSNUM >= 10700
virtual int NumProvidedSystems(void) const { return 1; }
#endif
virtual bool IsTunedToTransponder(const cChannel *Channel);
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>
@ -10,13 +10,11 @@
using namespace std;
const char *VERSION = "0.5.0-pre-20090611";
const char *VERSION = "0.5.0-pre-20090701";
const char *StreamTypes[st_Count] = {
"TS",
#if APIVERSNUM < 10703
"PES",
#endif
"PS",
"ES",
"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
@ -51,9 +51,7 @@ const cChannel *ChannelFromString(const char *String, int *Apid = NULL);
enum eStreamType {
stTS,
#if APIVERSNUM < 10703
stPES,
#endif
stPS,
stES,
stExtern,
@ -74,6 +72,7 @@ enum eSocketId {
siLive,
siReplay,
siLiveFilter,
siDataRespond,
si_Count
};

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

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

View File

@ -5,6 +5,8 @@
#include <vdr/ringbuffer.h>
#include <string>
namespace Streamdev {
class cTSExt;
class cExternRemux: public cTSRemux {
@ -21,4 +23,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

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/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

@ -2,20 +2,21 @@
#define VDR_STREAMDEV_TS2PESREMUX_H
#include "remux/tsremux.h"
#include <vdr/remux.h>
#include <vdr/ringbuffer.h>
#include "server/streamer.h"
#ifndef MAXTRACKS
#define MAXTRACKS 64
#endif
namespace Streamdev {
class cTS2PS;
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;
@ -30,4 +31,6 @@ public:
void Del(int Count) { m_ResultBuffer->Del(Count); }
};
} // namespace Streamdev
#endif // VDR_STREAMDEV_TS2PESREMUX_H

View File

@ -2,11 +2,15 @@
#define SC_PICTURE 0x00 // "picture header"
#define PID_MASK_HI 0x1F
#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;
@ -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.
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;
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;

View File

@ -4,29 +4,19 @@
#include "libdvbmpeg/transform.h"
#include <vdr/remux.h>
#ifndef NO_PICTURE
// Picture types:
#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 {
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 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);
@ -34,4 +24,6 @@ public:
static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType);
};
} // namespace Streamdev
#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>
@ -211,10 +211,8 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts)
const char* pType = type.c_str();
if (strcasecmp(pType, "PS") == 0) {
m_StreamType = stPS;
#if APIVERSNUM < 10703
} else if (strcasecmp(pType, "PES") == 0) {
m_StreamType = stPES;
#endif
} else if (strcasecmp(pType, "TS") == 0) {
m_StreamType = stTS;
} else if (strcasecmp(pType, "ES") == 0) {
@ -266,9 +264,7 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts)
{
case stTS: base += "TS/"; break;
case stPS: base += "PS/"; break;
#if APIVERSNUM < 10703
case stPES: base += "PES/"; break;
#endif
case stES: base += "ES/"; break;
case stExtern: base += "Extern/"; 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"
@ -8,6 +8,8 @@
#include "setup.h"
#include <vdr/tools.h>
#include <vdr/videodir.h>
#include <vdr/menu.h>
#include <tools/select.h>
#include <string.h>
#include <ctype.h>
@ -28,13 +30,20 @@
563: Recording not available (currently?)
*/
enum eDumpModeStreamdev { dmsdAll, dmsdPresent, dmsdFollowing, dmsdAtTime, dmsdFromToTime };
// --- cLSTEHandler -----------------------------------------------------------
class cLSTEHandler
{
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,
EndEvent, EndChannel, EndEPG };
#endif /* PARENTALRATING */
cConnectionVTP *m_Client;
cSchedulesLock *m_SchedulesLock;
const cSchedules *m_Schedules;
@ -44,6 +53,7 @@ private:
char *m_Error;
eStates m_State;
bool m_Traverse;
time_t m_ToTime;
public:
cLSTEHandler(cConnectionVTP *Client, const char *Option);
~cLSTEHandler();
@ -59,10 +69,12 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
m_Errno(0),
m_Error(NULL),
m_State(Channel),
m_Traverse(false)
m_Traverse(false),
m_ToTime(0)
{
eDumpMode dumpmode = dmAll;
eDumpModeStreamdev dumpmode = dmsdAll;
time_t attime = 0;
time_t fromtime = 0;
if (m_Schedules != NULL && *Option) {
char buf[strlen(Option) + 1];
@ -70,13 +82,13 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
const char *delim = " \t";
char *strtok_next;
char *p = strtok_r(buf, delim, &strtok_next);
while (p && dumpmode == dmAll) {
while (p && dumpmode == dmsdAll) {
if (strcasecmp(p, "NOW") == 0)
dumpmode = dmPresent;
dumpmode = dmsdPresent;
else if (strcasecmp(p, "NEXT") == 0)
dumpmode = dmFollowing;
dumpmode = dmsdFollowing;
else if (strcasecmp(p, "AT") == 0) {
dumpmode = dmAtTime;
dumpmode = dmsdAtTime;
if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
if (isnumber(p))
attime = strtol(p, NULL, 10);
@ -90,6 +102,39 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
m_Error = strdup("Missing time");
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) {
cChannel* Channel = NULL;
if (isnumber(p))
@ -129,16 +174,29 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
if (m_Schedule != NULL && m_Schedule->Events() != NULL) {
switch (dumpmode) {
case dmAll: m_Event = m_Schedule->Events()->First();
case dmsdAll: m_Event = m_Schedule->Events()->First();
m_Traverse = true;
break;
case dmPresent: m_Event = m_Schedule->GetPresentEvent();
case dmsdPresent: m_Event = m_Schedule->GetPresentEvent();
break;
case dmFollowing: m_Event = m_Schedule->GetFollowingEvent();
case dmsdFollowing: m_Event = m_Schedule->GetFollowingEvent();
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;
}
}
}
@ -227,7 +285,11 @@ bool cLSTEHandler::Next(bool &Last)
break;
case Vps:
#ifdef USE_PARENTALRATING
m_State = Content;
#else
m_State = EndEvent;
#endif /* PARENTALRATING */
if (m_Event->Vps())
#ifdef __FreeBSD__
return m_Client->Respond(-215, "V %d", m_Event->Vps());
@ -238,9 +300,26 @@ bool cLSTEHandler::Next(bool &Last)
return Next(Last);
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:
if (m_Traverse)
if (m_Traverse) {
m_Event = m_Schedule->Events()->Next(m_Event);
if ((m_Event != NULL) && (m_ToTime != 0) && (m_Event->StartTime() > m_ToTime)) {
m_Event = NULL;
}
}
else
m_Event = NULL;
@ -377,7 +456,7 @@ bool cLSTCHandler::Next(bool &Last)
}
}
if (i < Channels.MaxNumber())
if (i < Channels.MaxNumber() + 1)
Last = false;
}
@ -468,6 +547,181 @@ bool cLSTTHandler::Next(bool &Last)
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(void):
@ -476,12 +730,16 @@ cConnectionVTP::cConnectionVTP(void):
m_LiveStreamer(NULL),
m_FilterSocket(NULL),
m_FilterStreamer(NULL),
m_RecSocket(NULL),
m_DataSocket(NULL),
m_LastCommand(NULL),
m_StreamType(stTSPIDS),
m_FiltersSupport(false),
m_RecPlayer(NULL),
m_LSTEHandler(NULL),
m_LSTCHandler(NULL),
m_LSTTHandler(NULL)
m_LSTTHandler(NULL),
m_LSTRHandler(NULL)
{
}
@ -491,11 +749,15 @@ cConnectionVTP::~cConnectionVTP()
free(m_LastCommand);
delete m_LiveStreamer;
delete m_LiveSocket;
delete m_RecSocket;
delete m_FilterStreamer;
delete m_FilterSocket;
delete m_DataSocket;
delete m_LSTTHandler;
delete m_LSTCHandler;
delete m_LSTEHandler;
delete m_LSTRHandler;
delete m_RecPlayer;
}
inline bool cConnectionVTP::Abort(void) const
@ -548,7 +810,7 @@ bool cConnectionVTP::Command(char *Cmd)
}
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, "LSTC") == 0) return CmdLSTC(param);
@ -561,7 +823,9 @@ bool cConnectionVTP::Command(char *Cmd)
if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param);
else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(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, "PLAY") == 0) return CmdPLAY(param);
else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(param);
else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(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, "SUSP") == 0) return CmdSUSP();
// 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, "NEWT") == 0) return CmdNEWT(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
return Respond(500, "Unknown Command \"%s\"", Cmd);
}
@ -595,12 +866,10 @@ bool cConnectionVTP::CmdCAPS(char *Opts)
return Respond(220, "Capability \"%s\" accepted", Opts);
}
#if APIVERSNUM < 10703
if (strcasecmp(Opts, "PES") == 0) {
m_StreamType = stPES;
return Respond(220, "Capability \"%s\" accepted", Opts);
}
#endif
if (strcasecmp(Opts, "EXTERN") == 0) {
m_StreamType = stExtern;
@ -648,7 +917,7 @@ bool cConnectionVTP::CmdPORT(char *Opts)
if (ep == Opts || !isspace(*ep))
return Respond(500, "Use: PORT Id Destination");
if (id != siLive && id != siLiveFilter)
if (id >= si_Count)
return Respond(501, "Wrong connection id %d", id);
Opts = skipspace(ep);
@ -676,7 +945,8 @@ bool cConnectionVTP::CmdPORT(char *Opts)
isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport);
if (id == siLiveFilter) {
switch (id) {
case siLiveFilter:
m_FiltersSupport = true;
if(m_FilterStreamer)
m_FilterStreamer->Stop();
@ -696,8 +966,9 @@ bool cConnectionVTP::CmdPORT(char *Opts)
m_FilterStreamer->Activate(true);
return Respond(220, "Port command ok, data connection opened");
}
break;
case siLive:
if(m_LiveSocket && m_LiveStreamer)
m_LiveStreamer->Stop();
delete m_LiveSocket;
@ -716,6 +987,70 @@ bool cConnectionVTP::CmdPORT(char *Opts)
m_LiveStreamer->Start(m_LiveSocket);
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)
@ -749,6 +1084,32 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
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)
{
int pid;
@ -844,6 +1205,13 @@ bool cConnectionVTP::CmdABRT(char *Opts)
DELETENULL(m_FilterStreamer);
DELETENULL(m_FilterSocket);
break;
case siReplay:
DELETENULL(m_RecPlayer);
DELETENULL(m_RecSocket);
break;
case siDataRespond:
DELETENULL(m_DataSocket);
break;
default:
return Respond(501, "Wrong connection id %d", id);
break;
@ -881,7 +1249,8 @@ bool cConnectionVTP::CmdLSTX(cHandler *&Handler, char *Option)
Handler = new cHandler(this, Option);
}
bool last, result = false;
bool last = false;
bool result = false;
if (Handler != NULL)
result = Handler->Next(last);
else
@ -907,11 +1276,66 @@ bool cConnectionVTP::CmdLSTT(char *Option)
return CmdLSTX(m_LSTTHandler, Option);
}
bool cConnectionVTP::CmdLSTR(char *Option)
{
return CmdLSTX(m_LSTRHandler, Option);
}
// Functions adopted from SVDRP
#define INIT_WRAPPER() bool _res
#define Reply(c,m...) _res = Respond(c,m)
#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)
{
INIT_WRAPPER();
@ -992,51 +1416,235 @@ bool cConnectionVTP::CmdDELT(const char *Option)
EXIT_WRAPPER();
}
/*bool cConnectionVTP::CmdLSTR(char *Option) {
bool cConnectionVTP::CmdNEXT(const char *Option)
{
INIT_WRAPPER();
bool recordings = Recordings.Load();
Recordings.Sort();
if (*Option) {
if (isnumber(Option)) {
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
if (recording) {
if (recording->Summary()) {
char *summary = strdup(recording->Summary());
Reply(250, "%s", strreplace(summary,'\n','|'));
free(summary);
cTimer *t = Timers.GetNextActiveTimer();
if (t) {
time_t Start = t->StartTime();
int Number = t->Index() + 1;
if (!*Option)
Reply(250, "%d %s", Number, *TimeToString(Start));
else if (strcasecmp(Option, "ABS") == 0)
Reply(250, "%d %ld", Number, Start);
else if (strcasecmp(Option, "REL") == 0)
Reply(250, "%d %ld", Number, Start - time(NULL));
else
Reply(501, "Unknown option: \"%s\"", Option);
}
else
Reply(550, "No summary availabe");
}
else
Reply(550, "Recording \"%s\" not found", Option);
}
else
Reply(501, "Error in recording number \"%s\"", Option);
}
else if (recordings) {
cRecording *recording = Recordings.First();
while (recording) {
Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
recording = Recordings.Next(recording);
}
}
else
Reply(550, "No recordings available");
Reply(550, "No active timers");
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();
if (*Option) {
if (isnumber(Option)) {
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
if (recording) {
if (recording->Delete())
cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
if (!rc) {
if (recording->Delete()) {
Reply(250, "Recording \"%s\" deleted", Option);
::Recordings.DelByName(recording->FileName());
}
else
Reply(554, "Error while deleting recording!");
}
else
Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1);
}
else
Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
}
@ -1046,7 +1654,55 @@ bool cConnectionVTP::CmdDELR(char *Option) {
else
Reply(501, "Missing recording number");
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, ...)
{

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,6 +10,7 @@ class cStreamdevFilterStreamer;
class cLSTEHandler;
class cLSTCHandler;
class cLSTTHandler;
class cLSTRHandler;
class cConnectionVTP: public cServerConnection {
friend class cLSTEHandler;
@ -21,16 +23,19 @@ private:
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;
// Members adopted for SVDRP
cRecordings Recordings;
cLSTEHandler *m_LSTEHandler;
cLSTCHandler *m_LSTCHandler;
cLSTTHandler *m_LSTTHandler;
cLSTRHandler *m_LSTRHandler;
protected:
template<class cHandler>
@ -51,7 +56,9 @@ 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 CmdADDP(char *Opts);
bool CmdDELP(char *Opts);
bool CmdADDF(char *Opts);
@ -64,14 +71,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

@ -4,6 +4,7 @@
#include <libsi/descriptor.h>
#include "remux/ts2ps.h"
#include "remux/ts2pes.h"
#include "remux/ts2es.h"
#include "remux/extern.h"
@ -13,7 +14,7 @@
#include "server/livefilter.h"
#include "common.h"
#define TSPATREPACKER
using namespace Streamdev;
// --- cStreamdevLiveReceiver -------------------------------------------------
@ -64,6 +65,8 @@ private:
int pmtPid;
int pmtSid;
int pmtVersion;
uchar tspat_buf[TS_SIZE];
cStreamdevBuffer siBuffer;
const cChannel *m_Channel;
cStreamdevLiveStreamer *m_Streamer;
@ -73,9 +76,11 @@ 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\")\n", Channel->Name());
assert(Streamer);
@ -85,6 +90,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[] = {
@ -224,34 +252,18 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i
if (0 != (pmtPid = assoc.getPid())) {
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];
uint8_t *tmp;
static uint8_t ccounter = 0;
ccounter = (ccounter + 1) % 16;
memset(tspat_buf, 0xff, TS_SIZE);
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[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[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[11] = 0x0; // Section number
tspat_buf[12] = 0x0; // Last section number
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)
@ -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[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);
int written = siBuffer.PutTS(tspat_buf, TS_SIZE);
if (written != TS_SIZE)
siBuffer.ReportOverflow(TS_SIZE - written);
if (pmtPid != prevPmtPid) {
m_Streamer->SetPids(pmtPid);
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 != pmt.getVersionNumber()) {
Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n");
Del(pmtPid, 0x02);
cFilter::Del(pmtPid, 0x02);
pmtPid = 0; // this triggers PAT scan
}
return;
@ -329,12 +340,7 @@ cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Paramet
m_Device(NULL),
m_Receiver(NULL),
m_PatFilter(NULL),
#if APIVERSNUM < 10703
m_PESRemux(NULL),
#endif
m_ESRemux(NULL),
m_PSRemux(NULL),
m_ExtRemux(NULL)
m_Remux(NULL)
{
}
@ -347,12 +353,7 @@ cStreamdevLiveStreamer::~cStreamdevLiveStreamer()
DELETENULL(m_PatFilter);
}
DELETENULL(m_Receiver);
#if APIVERSNUM < 10703
delete m_PESRemux;
#endif
delete m_ESRemux;
delete m_PSRemux;
delete m_ExtRemux;
delete m_Remux;
}
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();
if (Apid != 0)
pid = Apid;
m_ESRemux = new cTS2ESRemux(pid);
m_Remux = new cTS2ESRemux(pid);
return SetPids(pid);
}
#if APIVERSNUM < 10703
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(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids());
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
#endif
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());
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
@ -490,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
return true;
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);
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)
{
switch (m_StreamType) {
case stTS:
case stTSPIDS:
return cStreamdevStreamer::Put(Data, Count);
#if APIVERSNUM < 10703
case stPES:
return m_PESRemux->Put(Data, Count);
#endif
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);
#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)
{
switch (m_StreamType) {
case stTS:
case stTSPIDS:
if (m_Remux)
m_Remux->Del(Count);
else
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)

View File

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

View File

@ -201,10 +201,8 @@ std::string cHtmlChannelList::StreamTypeMenu()
(std::string) "[<a href=\"/TS/" + self + "\">TS</a>] ");
typeMenu += (streamType == stPS ? (std::string) "[PS] " :
(std::string) "[<a href=\"/PS/" + self + "\">PS</a>] ");
#if APIVERSNUM < 10703
typeMenu += (streamType == stPES ? (std::string) "[PES] " :
(std::string) "[<a href=\"/PES/" + self + "\">PES</a>] ");
#endif
typeMenu += (streamType == stES ? (std::string) "[ES] " :
(std::string) "[<a href=\"/ES/" + self + "\">ES</a>] ");
typeMenu += (streamType == stExtern ? (std::string) "[Extern] " :
@ -343,10 +341,8 @@ std::string cHtmlChannelList::ItemText()
switch (streamType) {
case stTS: suffix = (std::string) ".ts"; break;
case stPS: suffix = (std::string) ".vob"; break;
#if APIVERSNUM < 10703
// for Network Media Tank
case stPES: suffix = (std::string) ".vdr"; break;
#endif
default: suffix = "";
}
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>
@ -14,6 +14,13 @@
#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,
@ -95,14 +102,13 @@ void cStreamdevWriter::Action(void)
cStreamdevStreamer::cStreamdevStreamer(const char *Name):
cThread(Name),
m_Running(false),
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()
@ -116,7 +122,6 @@ void cStreamdevStreamer::Start(cTBSocket *Socket)
{
Dprintf("start streamer\n");
m_Writer = new cStreamdevWriter(Socket, this);
m_Running = true;
Attach();
}
@ -135,9 +140,8 @@ void cStreamdevStreamer::Stop(void)
Dprintf("stopping streamer\n");
Cancel(3);
}
if (m_Running) {
if (m_Writer) {
Detach();
m_Running = false;
DELETENULL(m_Writer);
}
}
@ -152,8 +156,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.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
@ -16,8 +16,34 @@ class cStreamdevStreamer;
#define TS_SIZE 188
#endif
#define STREAMERBUFSIZE MEGABYTE(4)
#define WRITERBUFSIZE KILOBYTE(256)
#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 -------------------------------------------------------
@ -38,15 +64,14 @@ public:
class cStreamdevStreamer: public cThread {
private:
bool m_Running;
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);
@ -57,10 +82,10 @@ public:
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); }

View File

@ -3,12 +3,11 @@
*
* 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 <vdr/tools.h>
#include "remux/extern.h"
#include "streamdev-server.h"
#include "server/setup.h"
#include "server/server.h"